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.
Server
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;
};
|
Server
1
2
3
4
5
6
7
| initializer.RegisterBeforeCreateGroup(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.CreateGroupRequest) (*api.CreateGroupRequest, error) {
if containsProfanity(in.Name) {
return nil, runtime.NewError("profanity detected", 3)
}
return in, nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Before deleting a group
#
This hook denies an attempt to delete a group that still contains more than 1 user.
Server
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;
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
| initializer.RegisterBeforeDeleteGroup(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.DeleteGroupRequest) (*api.DeleteGroupRequest, error) {
groupUsers, _, err := nk.GroupUsersList(ctx, in.GroupId, 100, nil, "")
if err != nil {
return nil, runtime.NewError("error retrieving group users", 13)
}
if len(groupUsers) > 1 {
return nil, runtime.NewError("group still has users", 9)
}
return in, nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
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.
Server
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);
});
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| initializer.RegisterAfterAddFriends(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.AddFriendsRequest) error {
username, ok := ctx.Value(runtime.RUNTIME_CTX_USERNAME).(string)
if !ok {
return runtime.NewError("error getting username", 13)
}
for _, friendId := range in.Ids {
content := map[string]interface{} {
"message": fmt.Sprintf("You have received a friend request from %s", username),
}
nk.NotificationSend(ctx, friendId, "Friend Request Received", content, 1, nil, true)
}
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
After leaving a group
#
This hook sends a message to a group notifying the group users that a particular user has left.
Server
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);
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| initializer.RegisterAfterLeaveGroup(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.LeaveGroupRequest) error {
username, ok := ctx.Value(runtime.RUNTIME_CTX_USERNAME).(string)
if !ok {
return runtime.NewError("error getting username", 13)
}
channelId, err := nk.ChannelIdBuild(ctx, nil, in.GroupId, runtime.Group)
if err != nil {
return runtime.NewError("error getting channel id", 13)
}
content := map[string]interface{}{
"message": fmt.Sprintf("%s left the group.", username),
}
nk.ChannelMessageSend(ctx, channelId, content, nil, nil, true)
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
After authenticating by device ID
#
This hook rewards a player with 10 coins (in their virtual wallet whenever they authenticate by device ID.
Server
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);
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| initializer.RegisterAfterAuthenticateDevice(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, out *api.Session, in *api.AuthenticateDeviceRequest) error {
// Give the player 10 coins for logging in
userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
if !ok {
return runtime.NewError("error getting userId", 13)
}
changeset := map[string]int64{
"coins": 10,
}
nk.WalletUpdate(ctx, userId, changeset, nil, true)
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
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.
Server
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);
});
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| initializer.RegisterLeaderboardReset(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, leaderboard *api.Leaderboard, reset int64) error {
// Reward top 3 players with 1,000 coins and send them a notification telling them what their rank was
topRecords, _, _, _, err := nk.LeaderboardRecordsList(ctx, leaderboard.Id, nil, 3, "", 0)
if err != nil {
return runtime.NewError("error getting leaderboard records", 13)
}
for _, record := range topRecords {
changeset := map[string]int64 {
"coins": 1000,
}
content := map[string]interface{} {
"message": fmt.Sprintf("Well done, you ranked %d!", record.Rank),
}
nk.WalletUpdate(ctx, record.OwnerId, changeset, nil, true)
nk.NotificationSend(ctx, record.OwnerId, "Congratulations", content, 2, "", true)
}
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
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.
Server
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]);
};
|
Server
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| initializer.RegisterTournamentEnd(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, tournament *api.Tournament, end, reset int64) error {
// Reward tournament leader with 10,000 coins and a rare item
topRecord, _, _, _, err := nk.TournamentRecordsList(ctx, tournament.Id, nil, 1, "", 0)
if err != nil {
return runtime.NewError("error getting tournament records", 13)
}
winnerId := topRecord[0].OwnerId
// Give the player 10,000 coins
changeset := map[string]int64 {
"coins": 10000,
}
nk.WalletUpdate(ctx, winnerId, changeset, nil, true)
// Get the player's existing inventory
storageRead := &runtime.StorageRead{
Collection: "inventory",
Key: winnerId,
UserID: winnerId,
}
result, err := nk.StorageRead(ctx, []*runtime.StorageRead { storageRead })
if err != nil {
return runtime.NewError("error reading player inventory", 13)
}
var inventory map[string]int
if len(result) > 0 {
if err := json.Unmarshal([]byte(result[0].Value), &inventory); err != nil {
logger.Error("error unmarshaling inventory", err)
return runtime.NewError("error unmarshaling inventory", 13)
}
} else {
inventory = make(map[string]int)
}
// Add the rare item or increase quantity
if _, ok := inventory["rare-sword"]; !ok {
inventory["rare-sword"] = 1
} else {
inventory["rare-sword"] += 1
}
// Write the updated inventory to the storage engine
inventoryJson, err := json.Marshal(inventory)
if err != nil {
return runtime.NewError("error marshaling inventory", 13)
}
storageWrite := &runtime.StorageWrite{
Collection: "inventory",
Key: winnerId,
UserID: winnerId,
PermissionWrite: 0,
PermissionRead: 1,
Value: string(inventoryJson),
}
_, err = nk.StorageWrite(ctx, []*runtime.StorageWrite { storageWrite })
if err != nil {
return runtime.NewError("error writing inventory", 13)
}
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
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.
Server
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;
};
|
Server
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
| initializer.RegisterBeforeRt("ChannelJoin", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *rtapi.Envelope) (*rtapi.Envelope, error) {
envelope, ok := in.Message.(*rtapi.Envelope_ChannelJoin)
if !ok {
return nil, runtime.NewError("error getting envelope as ChannelJoin envelope", 13)
}
userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
if !ok {
return nil, runtime.NewError("error getting userId", 13)
}
// If the channel join is a DirectMessage type, check to see if the user is friends with the recipient first
if envelope.ChannelJoin.Type == 2 {
state := 0
friends, _, err := nk.FriendsList(ctx, userId, 100, &state, "")
if err != nil {
return nil, runtime.NewError("error getting friends list", 13)
}
isFriends := false
for _, friend := range friends {
if friend.User.Id == envelope.ChannelJoin.Target {
isFriends = true
break
}
}
if !isFriends {
return nil, runtime.NewError("you cannot direct message someone you are not friends with.", 9)
}
}
return in, nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
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.
Server
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 message to the channel after leaving, notifying others that the user left
nk.channelMessageSend(input.channelLeave.channelId, { message: `${ctx.userId} left the channel.` }, "", "", true);
};
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| initializer.RegisterAfterRt("ChannelLeave", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, out, in *rtapi.Envelope) error {
envelope, ok := in.Message.(*rtapi.Envelope_ChannelLeave)
if !ok {
return runtime.NewError("error getting envelope as ChannelLeave envelope", 13)
}
userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
if !ok {
return runtime.NewError("error getting userId", 13)
}
// Send a message to the channel after leaving, notifying others that the user left
content := map[string]interface{}{
"message": fmt.Sprintf("%s left the channel.", userId),
}
nk.ChannelMessageSend(ctx, envelope.ChannelLeave.ChannelId, content, "", "", true)
return nil
})
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.