[go: up one dir, main page]

In-app Notifications #

In-app notifications make it easy to broadcast a message to one or more users. They are great for sending announcements, alerts, or notices of in-game rewards and gifts.

A notification can be stored until read when the app is next opened or it can be pushed so only a connected user will see it. You can also use notifications to trigger custom actions within your game and change client behavior.

These notifications are viewed within the app which makes them a great companion to push notifications viewed outside the app.

Send notifications #

You can send a notification to one or more users with server-side code. It can be sent to any user in the game - there’s no need to be friends to be able to exchange messages. A number of notifications are also sent by the server implicitly on certain events.

Each notification has a code which is used to categorize it. The code you choose for your notifications must start at 0 and increase upwards. See below for reserved message codes.

A notification has a content object which will be encoded as JSON.

Notifications can be marked as persistent when sent. A non-persistent message will only be received by a client which is currently connected to the server (i.e. a user who is online). If you want to make sure a notification is never lost before it’s read, it should be marked as persistent when sent.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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)
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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)
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);

Receive notifications #

A callback can be registered for notifications received when a client is connected. The handler will be called whenever a notification is received as long as the client socket remains connected. When multiple messages are returned (batched for performance) the handler will be called once for each notification.

Client
1
2
3
4
socket.onnotification = (notification) => {
  console.log("Received %o", notification);
  console.log("Notification content %s", notification.content);
}
Client
1
2
3
4
5
socket.ReceivedNotification += notification =>
{
    Console.WriteLine("Received: {0}", notification);
    Console.WriteLine("Notification content: '{0}'", notification.Content);
};
Client
1
2
3
4
socket.onNotifications = { notification in
    print("Received: \(notification)")
    print("Notification content: \(notification.content)")
}
Client
1
2
3
4
socket.onNotifications.listen((notification) {
  print("Received: $notification");
  print("Notification content: ${notification.content}");
});
Client
1
2
3
4
5
6
7
rtListener->setNotificationsCallback([](const NNotificationList& notifications)
{
    for (auto& notification : notifications.notifications)
    {
        std::cout << "Notification content " << notification.content << std::cout;
    }
});
Client
1
2
3
4
5
6
7
8
9
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());
        }
    }
};
Client
1
2
3
4
5
6
7
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)
Client
1
2
3
4
5
6
7
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.received_notification.connect(self._on_notification)

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    print(p_notification)
    print(p_notification.content)
Client
1
2
3
4
5
local group_id = "<group id>"
socket.on_notification(function(message)
  print("Received notification!")
  pprint(message)
end)

Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

List notifications #

You can list notifications which were received when the user was offline. These notifications are ones which were marked “persistent” when sent. The exact logic depends on your game or app but we suggest you retrieve notifications after a client reconnects. You can then display a UI within your game or app with the list.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
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);
Client
1
2
3
4
5
var result = await client.ListNotificationsAsync(session, 10);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
Client
1
2
3
4
5
let result = try await client.listNotifications(session: session, limit: 10)

for notification in result.notifications {
    print("Notification content: \(notification.content)")
}
Client
1
2
3
4
5
6
7
8
final result = await client.listNotifications(
  session: session,
  limit: 10,
);

for (final notification in result.notifications) {
  print('Notification content: ${notification.content}');
}
Client
1
2
3
4
5
6
7
8
9
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

client->listNotifications(session, 10, opt::nullopt, successCallback);
Client
1
2
3
4
NotificationList notifications = client.listNotifications(session, 10).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
Client
1
2
3
4
5
6
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])
Client
1
2
3
4
5
6
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 10)
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])
Client
1
2
3
4
5
GET /v2/notification?limit=10
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
8
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

A list of notifications can be retrieved in batches of up to 100 at a time. To retrieve all messages you should accumulate them with the cacheable cursor. You can keep this cursor on the client and use it when the user reconnects to catch up on any notifications they may have missed while offline.

You will usually want to list only 100 notifications at a time, otherwise you might cause user fatigue. A good solution could be to have the UI fetch the next 100 notifications when the user scrolls to the bottom of your UI panel.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=100&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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("");
Client
1
2
3
4
5
6
7
8
9
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);
    }
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var allNotifications = [ApiNotification]()

func accumulateNotifications(cursor: String) async {
    do {
        let result = try await client.listNotifications(session: session, limit: 100, cacheableCursor: cursor)
        allNotifications.append(contentsOf: result.notifications)
        if !result.cacheableCursor.isEmpty {
            await accumulateNotifications(cursor: result.cacheableCursor)
        }
    } catch {
        print("Failed to fetch notifications: \(error.localizedDescription)")
    }
}

await accumulateNotifications(cursor: "")
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
final allNotifications = [];

Future accumulateNotifications(String cursor) async {
  final result = await client.listNotifications(
    session: session,
    limit: 100,
    cursor: cursor,
  );
  allNotifications.addAll(result.notifications);
  if (result.cursor != null && result.cursor!.isNotEmpty) {
    await accumulateNotifications(result.cursor!);
  }
}

await accumulateNotifications('');
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 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("");
Client
1
2
3
4
5
6
7
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());
    }
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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")
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 1)
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 = await client.list_notifications_async(session, 10, result.cacheable_cursor)
    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")
Client
1
2
3
4
5
GET /v2/notification?limit=100&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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

It can be useful to retrieve only notifications which have been added since the list was last retrieved by a client. This can be done with the cacheable cursor returned with each list message. Sending the cursor through a new list operation will retrieve only notifications newer than those seen.

The cacheable cursor marks the position of the most recent notification retrieved. We recommend you store the cacheable cursor in device storage and use it when the client makes its next request for recent notifications.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
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);
});
Client
1
2
3
4
5
6
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);
}
Client
1
2
3
4
5
6
7
let cacheableCursor = "<cacheableCursor>"

var result = try await client.listNotifications(session: session, limit: 10, cacheableCursor: cacheableCursor)

for notification in result.notifications {
    print("Notification content: \(notification.content)")
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const cacheableCursor = '<cacheableCursor>';

final result = await client.listNotifications(
  session: session,
  limit: 10,
  cursor: cacheableCursor,
);

for (final notification in result.notifications) {
  print('Notification content: ${notification.content}');
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);
Client
1
2
3
4
5
String cacheableCursor = "<cacheableCursor>";
NotificationList notifications = client.listNotifications(session, 10, cacheableCursor).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
Client
1
2
3
4
5
6
7
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])
Client
1
2
3
4
5
6
7
var cursor = "<cacheable-cursor>";
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 10, cursor)
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])
Client
1
2
3
4
5
GET /v2/notification?limit=10&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

Delete notifications #

You can delete one or more notifications from the client. This is useful to purge notifications which have been read or consumed by the user and prevent a build up of old messages. When a notification is deleted all record of it is removed from the system and it cannot be restored.

Client
1
2
curl -X DELETE "http://127.0.0.1:7350/v2/notification?ids=<notificationId>&ids=<notificationId>" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
const notificationIds = ["<notificationId>"];
await client.deleteNotifications(session, notificationIds);
Client
1
2
var notificationIds = new[] {"<notificationId>"};
await client.DeleteNotificationsAsync(session, notificationIds);
Client
1
2
3
var notificationIds = ["<notificationId>"]

try await client.deleteNotifications(session: session, ids: notificationIds)
Client
1
2
3
4
5
6
final notificationIds = ['<notificationId>'];

await client.deleteNotifications(
  session: session,
  notificationIds: notificationIds,
);
Client
1
client->deleteNotifications(session, { "<notificationId>" });
Client
1
2
String[] notificationIds = new String[] {"<notificationId>"};
client.deleteNotifications(session, notificationIds).get();
Client
1
2
3
4
5
6
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")
Client
1
2
3
4
5
6
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = await client.delete_notifications_async(session, notification_ids)
if delete.is_exception():
    print("An error occurred: %s" % delete)
    return
print("Deleted")
Client
1
2
3
4
5
DELETE /v2/notification?ids=<notificationId>&ids=<notificationId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
local ids = { "<notification-id>" }
local result = client.delete_notifications(ids)
if result.error then
  print(result.message)
  return
end

Notification codes #

The server reserves all codes that are less than or equal to 0 for messages sent implicitly on certain events. You can define your own notification codes by simply using values greater than 0.

The code is useful to decide how to display the notification in your UI.

CodePurpose
0Reserved
-1Message received from user X while offline or not in channel.
-2User X wants to add you as a friend.
-3User X accepted your friend invite.
-4You’ve been accepted to X group.
-5User X wants to join your group.
-6Your friend X has just joined the game.
-7Final notifications to sockets closed via the single_socket configuration.
-8You’ve been banned.