New product announcement 🎉 Satori - LiveOps to know your players, deliver features, run experiments and schedule events

Using Hooks

Hooks are a feature of the Nakama server runtime that allow you to run custom server code before or after certain server events happen. This can be useful in a number of scenarios, some of which are given below as examples.

For more information on hooks and how they can be used see the hooks documentation and for a full list of server messages that can benefit from hooks see the message names list.

All registration calls below are to be run inside the InitModule function.

Before hooks

These hooks run before a particular server runtime event occurs. This can be used to modify the input to a particular function or take additional actions before it occurs.

Before creating a group

This hook checks that a group name does not contain profanity before allowing it to be created. Note that profanity checking functionality is not provided by Nakama.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
initializer.registerBeforeCreateGroup(beforeCreateGroup);

let beforeCreateGroup : nkruntime.BeforeHookFunction<nkruntime.CreateGroupRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.CreateGroupRequest) : nkruntime.CreateGroupRequest {
  // Check the group name does not contain profanity (containsProfanity implementation not provided)
  if (containsProfanity(data.name)) {
    throw new Error("Profanity detected.")
  }

  return data;
};

Before deleting a group

This hook denies an attempt to delete a group that still contains more than 1 user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
initializer.registerBeforeDeleteGroup(beforeDeleteGroup);

let beforeDeleteGroup : nkruntime.BeforeHookFunction<nkruntime.DeleteGroupRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.DeleteGroupRequest) : nkruntime.DeleteGroupRequest {
  // Deny the delete request if the group still has more than one user
  const result = nk.groupUsersList(data.groupId, null, null, null);
  if (result.groupUsers.length > 1) {
      throw new Error("Group still has users.")
  }

  return data;
};

After hooks

These hooks run after a particular server runtime event occurs. This can be used to respond to an event appropriately after it has happened.

After adding friends

This hook sends a notification to each user that has been added as a friend by another user, letting them know that they have received a friend request.

1
2
3
4
5
6
7
8
initializer.registerAfterAddFriends(afterAddFriends);

let afterAddFriends: nkruntime.AfterHookFunction<void, nkruntime.AddFriendsRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: void, request: nkruntime.AddFriendsRequest) {
  // Notify each user that they have received a friend request
  request.ids.forEach(function (id) {
    nk.notificationSend(id, 'Friend Request Received', { message: `You have received a friend request from ${ctx.username}.` }, 1, null, true);
  });
};

After leaving a group

This hook sends a message to a group notifying the group users that a particular user has left.

1
2
3
4
5
6
7
initializer.registerAfterLeaveGroup(afterLeaveGroup);

let afterLeaveGroup: nkruntime.AfterHookFunction<void, nkruntime.LeaveGroupRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: void, request: nkruntime.LeaveGroupRequest) {
  // Send a message to the group to say the player left
  const channelId = nk.channelIdBuild(null, request.groupId, nkruntime.ChanType.Group);
  nk.channelMessageSend(channelId, { message: `${ctx.username} left the group.` }, null, null, true);
};

After authenticating by device ID

This hook rewards a player with 10 coins (in their virtual wallet whenever they authenticate by device ID.

1
2
3
4
5
6
initializer.registerAfterAuthenticateDevice(afterAuthenticateDevice);

let afterAuthenticateDevice: nkruntime.AfterHookFunction<nkruntime.Session, nkruntime.AuthenticateDeviceRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.Session, request: nkruntime.AuthenticateDeviceRequest) {
  // Give the player 10 coins for logging in
  nk.walletUpdate(ctx.userId, { coins: 10 }, null, true);
};

Event hooks

These hooks run after a particular event has happened, such as a leaderboard being reset or a tournament ending.

On leaderboard reset

This hook rewards the top 3 scoring players in a leaderboard with 1,000 coins whenever the leaderboard resets.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
initializer.registerLeaderboardReset(onLeaderboardReset);

let onLeaderboardReset : nkruntime.LeaderboardResetFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, leaderboard: nkruntime.Leaderboard, reset: number) {
  // Reward top 3 players with 1,000 coins and send them a notification telling them what their rank was
  const topRecords = nk.leaderboardRecordsList(leaderboard.id, null, 3, null, null);
  topRecords.records.forEach(function (record) {
    nk.walletUpdate(record.ownerId, { coins: 1000 }, null, true);
    nk.notificationSend(record.ownerId, 'Congratulations', { message: `Well done, you ranked ${record.rank}!` }, 2, null, true);
  });
};

On tournament end

This hook rewards the winner of a tournament with 10,000 coins and gives them a rare item when the tournament ends.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
initializer.registerTournamentEnd(onTournamentEnd);

let onTournamentEnd : nkruntime.TournamentEndFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, tournament: nkruntime.Tournament, end: number, reset: number) {
  // Reward tournament leader with 10,000 coins and a rare item
  const topRecord = nk.tournamentRecordsList(tournament.id, null, 1, null, null);
  const winnerId = topRecord.records[0].ownerId;

  // Give the player 10,000 coins
  nk.walletUpdate(winnerId, { coins: 10000 }, null, true);

  // Get the player's existing inventory
  const storageRead : nkruntime.StorageReadRequest = {
    collection: "inventory",
    key: winnerId,
    userId: winnerId
  };

  const result = nk.storageRead([storageRead]);
  let inventory = {};

  if (result.length > 0) {
    inventory = result[0].value;
  }

  // Add the rare item or increase quantity
  if (!inventory["rare-sword"]) {
    inventory["rare-sword"] = 1;
  } else {
    inventory["rare-sword"]++;
  }

  // Write the updated inventory to the storage engine
  const storageWrite : nkruntime.StorageWriteRequest = {
    collection: "inventory",
    key: winnerId,
    userId: winnerId,
    permissionWrite: 0,
    permissionRead: 1,
    value: inventory
  }

  nk.storageWrite([storageWrite]);
};

Before realtime hooks

These hooks run before a specific realtime message is processed by the server.

Before channel join

This hook checks to see if a user is trying to join a Direct Message channel for a non-friend. If so, it blocks their request to join the channel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
initializer.registerRtBefore("ChannelJoin", beforeChannelJoin);

let beforeChannelJoin : nkruntime.RtBeforeHookFunction<nkruntime.EnvelopeChannelJoin> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.EnvelopeChannelJoin) : nkruntime.EnvelopeChannelJoin | void {
  // If the channel join is a DirectMessage type, check to see if the user is friends with the recipient first
  if (envelope.channelJoin.type == nkruntime.ChanType.DirectMessage) {
    const result = nk.friendsList(ctx.userId, null, 0, null);
    const filtered = result.friends.filter(function (friend) {
      return friend.user.userId == envelope.channelJoin.target;
    });

    if (filtered.length == 0) {
      throw new Error("You cannot direct message someone you are not friends with.");
    }
  }

  return envelope;
};

After realtime hooks

These hooks run after a specific realtime message is processed by the server.

After channel leave

This hook lets a recipient know that the other user has left the channel after that user has left.

1
2
3
4
5
6
initializer.registerRtAfter("ChannelLeave", afterChannelLeave);

let afterChannelLeave : nkruntime.RtAfterHookFunction<nkruntime.EnvelopeChannelLeave> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, output: nkruntime.EnvelopeChannelLeave | null, input: nkruntime.EnvelopeChannelLeave) {
  // Send a notification to the channel after leaving, notifying others that the user left
  nk.channelMessageSend(input.channelLeave.channelId, { message: `${ctx.userId} left the channel.` }, null, null, true);
};