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.
All registration calls below are to be run inside the InitModule function.
For more information on hooks and how they can be used see the hooks documentation.
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.
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);letbeforeCreateGroup : 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)){thrownewError("Profanity detected.")}returndata;};
localfunctionbeforeCreateGroup(context,logger,nk,data)-- Check the group name does not contain profanity (containsProfanity implementation not provided)ifcontainsProfanity(data.name)thenerror("Profanity detected.")endreturndataendnk.register_req_before(beforeCreateGroup,"CreateGroup")
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);letbeforeDeleteGroup : 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
constresult=nk.groupUsersList(data.groupId,null,null,null);if(result.groupUsers.length>1){thrownewError("Group still has users.")}returndata;};
Server
1
2
3
4
5
6
7
8
9
10
11
12
initializer.RegisterBeforeDeleteGroup(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,in*api.DeleteGroupRequest)(*api.DeleteGroupRequest,error){groupUsers,_,err:=nk.GroupUsersList(ctx,in.GroupId,100,nil,"")iferr!=nil{returnnil,runtime.NewError("error retrieving group users",13)}iflen(groupUsers)>1{returnnil,runtime.NewError("group still has users",9)}returnin,nil})
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
localfunctionbeforeDeleteGroup(context,logger,nk,data)-- Deny the delete request if the group still has more than one userlocalresult,err=nk.group_users_list(data.groupId,nil,nil,nil)iferrthenerror("Error retrieving group users.")endif#result>1thenerror("Group still has users.")endreturndataendnk.register_req_before(beforeDeleteGroup,"DeleteGroup")
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);letafterAddFriends: 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(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,in*api.AddFriendsRequest)error{username,ok:=ctx.Value(runtime.RUNTIME_CTX_USERNAME).(string)if!ok{returnruntime.NewError("error getting username",13)}for_,friendId:=rangein.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)}returnnil})
Server
1
2
3
4
5
6
7
8
9
localfunctionafterAddFriends(context,logger,nk,data,request)-- Notify each user that they have received a friend requestfor_,idinipairs(request.ids)dolocalcontent={message="You have received a friend request from "..context.username}nk.notification_send(id,"Friend Request Received",content,1,nil,true)endendnk.register_after_add_friends(afterAddFriends)
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);letafterLeaveGroup: 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
constchannelId=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(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,in*api.LeaveGroupRequest)error{username,ok:=ctx.Value(runtime.RUNTIME_CTX_USERNAME).(string)if!ok{returnruntime.NewError("error getting username",13)}channelId,err:=nk.ChannelIdBuild(ctx,nil,in.GroupId,runtime.Group)iferr!=nil{returnruntime.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)returnnil})
Server
1
2
3
4
5
6
7
8
localfunctionafterLeaveGroup(context,logger,nk,data,request)-- Send a message to the group to say the player leftlocalchannelId=nk.channel_id_build(nil,request.groupId,nk.channel_type_group)localcontent={message=context.username.." left the group."}nk.channel_message_send(channelId,content,nil,nil,true)endnk.register_req_after(afterLeaveGroup,"LeaveGroup")
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);letafterAuthenticateDevice: 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(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.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{returnruntime.NewError("error getting userId",13)}changeset:=map[string]int64{"coins":10,}nk.WalletUpdate(ctx,userId,changeset,nil,true)returnnil})
Server
1
2
3
4
5
6
7
localfunctionafterAuthenticateDevice(context,logger,nk,data,request)-- Give the player 10 coins for logging inlocalchangeset={coins=10}nk.wallet_update(context.user_id,changeset,nil,true)endnk.register_req_after(afterAuthenticateDevice,"AuthenticateDevice")
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);letonLeaderboardReset : 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
consttopRecords=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);});};
initializer.RegisterLeaderboardReset(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,leaderboard*api.Leaderboard,resetint64)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)iferr!=nil{returnruntime.NewError("error getting leaderboard records",13)}for_,record:=rangetopRecords{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)}returnnil})
localfunctiononLeaderboardReset(context,logger,nk,leaderboard,reset)-- Reward top 3 players with 1,000 coins and send them a notification telling them what their rank waslocallimit=3localcursor=nillocalexpiry=nillocalownerId=nillocalresult,err=nk.leaderboard_records_list(leaderboard.id,ownerId,limit,cursor,expiry)iferrthenlogger.error("Error getting leaderboard records: "..err.message)returnendfor_,recordinipairs(result.records)dolocalchangeset={coins=1000}nk.wallet_update(record.owner_id,changeset,nil,true)localcontent={message="Well done, you ranked "..record.rank.."!"}nk.notification_send(record.owner_id,"Congratulations",content,2,nil,true)endendnk.register_leaderboard_reset(onLeaderboardReset)
initializer.registerTournamentEnd(onTournamentEnd);letonTournamentEnd : 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
consttopRecord=nk.tournamentRecordsList(tournament.id,null,1,null,null);constwinnerId=topRecord.records[0].ownerId;// Give the player 10,000 coins
nk.walletUpdate(winnerId,{coins: 10000},null,true);// Get the player's existing inventory
conststorageRead : nkruntime.StorageReadRequest={collection:"inventory",key: winnerId,userId: winnerId};constresult=nk.storageRead([storageRead]);letinventory={};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
conststorageWrite : nkruntime.StorageWriteRequest={collection:"inventory",key: winnerId,userId: winnerId,permissionWrite: 0,permissionRead: 1,value: inventory}nk.storageWrite([storageWrite]);};
initializer.RegisterTournamentEnd(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,tournament*api.Tournament,end,resetint64)error{// Reward tournament leader with 10,000 coins and a rare item
topRecord,_,_,_,err:=nk.TournamentRecordsList(ctx,tournament.Id,nil,1,"",0)iferr!=nil{returnruntime.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})iferr!=nil{returnruntime.NewError("error reading player inventory",13)}varinventorymap[string]intiflen(result)>0{iferr:=json.Unmarshal([]byte(result[0].Value),&inventory);err!=nil{logger.Error("error unmarshaling inventory",err)returnruntime.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)iferr!=nil{returnruntime.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})iferr!=nil{returnruntime.NewError("error writing inventory",13)}returnnil})
localfunctiononTournamentEnd(context,logger,nk,tournament,end,reset)-- Reward tournament leader with 10,000 coins and a rare itemlocallimit=1localcursor=nillocalexpiry=nillocalresult,err=nk.tournament_records_list(tournament.id,nil,limit,cursor,expiry)iferrthenlogger.error("Error getting tournament records: "..err.message)returnendif#result.records==0thenlogger.info("No records found for the tournament.")returnendlocalwinnerId=result.records[1].owner_id-- Give the player 10,000 coinslocalchangeset={coins=10000}localupdateErr=nk.wallet_update(winnerId,changeset,nil,true)ifupdateErrthenlogger.error("Error updating wallet: "..updateErr.message)returnend-- Get the player's existing inventorylocalstorageRead={collection="inventory",key=winnerId,user_id=winnerId}localstorageResult,storageErr=nk.storage_read({storageRead})ifstorageErrthenlogger.error("Error reading player inventory: "..storageErr.message)returnendlocalinventory={}if#storageResult>0theninventory=storageResult[1].valueend-- Add the rare item or increase quantityifnotinventory["rare-sword"]theninventory["rare-sword"]=1elseinventory["rare-sword"]=inventory["rare-sword"]+1end-- Write the updated inventory to the storage enginelocalstorageWrite={collection="inventory",key=winnerId,user_id=winnerId,permission_write=0,permission_read=1,value=inventory}localwriteErr=nk.storage_write({storageWrite})ifwriteErrthenlogger.error("Error writing inventory: "..writeErr.message)returnendendnk.register_tournament_end(onTournamentEnd)
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);letbeforeChannelJoin : 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){constresult=nk.friendsList(ctx.userId,null,0,null);constfiltered=result.friends.filter(function(friend){returnfriend.user.userId==envelope.channelJoin.target;});if(filtered.length==0){thrownewError("You cannot direct message someone you are not friends with.");}}returnenvelope;};
initializer.RegisterBeforeRt("ChannelJoin",func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,in*rtapi.Envelope)(*rtapi.Envelope,error){envelope,ok:=in.Message.(*rtapi.Envelope_ChannelJoin)if!ok{returnnil,runtime.NewError("error getting envelope as ChannelJoin envelope",13)}userId,ok:=ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)if!ok{returnnil,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
ifenvelope.ChannelJoin.Type==2{state:=0friends,_,err:=nk.FriendsList(ctx,userId,100,&state,"")iferr!=nil{returnnil,runtime.NewError("error getting friends list",13)}isFriends:=falsefor_,friend:=rangefriends{iffriend.User.Id==envelope.ChannelJoin.Target{isFriends=truebreak}}if!isFriends{returnnil,runtime.NewError("you cannot direct message someone you are not friends with.",9)}}returnin,nil})
localfunctionbeforeChannelJoin(context,logger,nk,envelope)-- If the channel join is a DirectMessage type, check to see if the user is friends with the recipient firstifenvelope.channel_join.type==nk.channel_type_direct_messagethenlocalfriends,err=nk.friends_list(context.user_id,nil,0,nil)iferrthenlogger.error("Error getting friends list: "..err.message)returnendlocalisFriend=falsefor_,friendinipairs(friends)doiffriend.user_id==envelope.channel_join.targetthenisFriend=truebreakendendifnotisFriendthenerror("You cannot direct message someone you are not friends with.")endendreturnenvelopeendnk.register_rt_before("ChannelJoin",ChannelJoin)
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);letafterChannelLeave : 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);};
initializer.RegisterAfterRt("ChannelLeave",func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,out,in*rtapi.Envelope)error{envelope,ok:=in.Message.(*rtapi.Envelope_ChannelLeave)if!ok{returnruntime.NewError("error getting envelope as ChannelLeave envelope",13)}userId,ok:=ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)if!ok{returnruntime.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)returnnil})
Server
1
2
3
4
5
6
7
localfunctionafterChannelLeave(context,logger,nk,output,input)-- Send a message to the channel after leaving, notifying others that the user leftlocalcontent={message=context.user_id.." left the channel."}nk.channel_message_send(input.channel_leave.channel_id,content,"","",true)endnk.register_req_after(afterChannelLeave,"ChannelLeave")