Send invite to friends from js client so they can receive party ID and join

Hello, i have trouble understanding how to send notifications to friends in nakama-js. Here is my usecase:

My user is friend with another one.
My user creates a party, and send notification to his friends with the party ID so they can join

In my React application, i use this code to create the party

const party = await socket.createParty(true, 2);

I then list my friends with the ‘listFriends’ function

const friends = await client.listFriends(session);

Once I have the list of my friends, i assumed i could send a notification to each of them with a function like ‘socket.sendNotification([friendIds], partyId)’, or ‘socket.inviteToParty([friendIds], partyId)’, but i couldn’t find anything that does that in the docs or in the Socket interface. I see functions that allow you to create, join, send data to a party but it’s not clear about how you can simply send notification to your friends to share the party ID so they can join. It seems pretty basic but i couldn’t find my use case even here on the forum.

What am I missing ? Is there any way to do that client side ? Or am i forced to write logic server-side ?

Thank you for the help

I managed to make it work by using firestore and make my own party invitation system.
Maybe i complicated things, but it works. Still interested to know a clean way to do it with the nakama-js client, if anyone have an idea :slightly_smiling_face:

Hello @Laguigue,

I’m glad you found a workaround, but here’s what you could do:

  1. Use a Direct Message (see: Real-time Chat - Heroic Labs Documentation).
    When the client receives a notification to join a private chat, you can check if the incoming request is from a friend, and if yes auto-join.
    The sender would have to listen for join events too so that it know when the other end has joined the chat, after which you could send the party ID.

  2. Create a custom RPC that uses the notification API (only exposed in the server runtime) - receive the target user(s) in the payload and send a notification each. It would require some server code but it’s pretty simple.

Best.

I thought about using the chat indeed after implementing my own solution, wasn’t sure about it, but now that you confirm i’ll definitely give it a try ! Thank you very much :slight_smile:

Will close the topic once i confirm this

No problem, no need to close the topic, others may find this useful or may have follow-up questions even if you get it working :slight_smile:

I made it work with direct messages :slight_smile:

Thank you !

In case you wonder how it’d work with a custom RPC, this is how I’m doing it:

  1. Send a request via RPC to the friend (only invitations by friends allowed in my case, not visible in this example)
  2. Friend sends an accept RPC.
  3. Server creates a match and sends a notification to both users with the match ID.
  4. Client accepts.

Sample Code

To invite a user using notifications:

func ChallengeUser(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, challengerID, opponentID string) error {
	userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)

	if !ok {
		return errNoUserIdFound
	}

	if opponentID == "" || opponentID == challengerID {
		return errors.New("invalid opponent ID")
	}

	notificationContent := map[string]interface{}{
		"challenger_id": challengerID,
	}

	if err := nk.NotificationSend(ctx, opponentID, "match_invitation", notificationContent, NotificationCodeInvitation, userId, false); err != nil {
		logger.Error("Failed to send challenge notification")
		return err
	}

	return nil
}

To respond to the inviting one on success/failure:

func ChallengeUserRpc() nakamaRpcFunc {
	return func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
		var data map[string]string
		if err := json.Unmarshal([]byte(payload), &data); err != nil {
			return "", errors.New("invalid payload")
		}

		challengerID := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
		opponentID := data["opponent_id"]

		if err := ChallengeUser(ctx, logger, nk, challengerID, opponentID); err != nil {
			return "", err
		}

		response := map[string]interface{}{
			"success":       true,
			"message":       "Challenge sent successfully",
			"challenger_id": challengerID,
			"opponent_id":   opponentID,
		}
		respBytes, _ := json.Marshal(response)

		return string(respBytes), nil
	}
}

To finally accept & send the notification to join a match to both users (in case of a 1v1):

func SendAcceptedNotifications(context context.Context, nk runtime.NakamaModule, logger runtime.Logger, challengerId string, userId string, matchId string) error {
	notificationContent := map[string]interface{}{
		"matchId": matchId,
	}

	if err := nk.NotificationSend(context, userId, "match_invitation", notificationContent, NotificationCodeInvitiationAccepted, challengerId, false); err != nil {
		logger.Error("Failed to send challenge notification")
		return err
	}

	if err := nk.NotificationSend(context, challengerId, "match_invitation", notificationContent, NotificationCodeInvitiationAccepted, userId, false); err != nil {
		logger.Error("Failed to send challenge notification")
		return err
	}

	return nil
}

func AcceptChallengeRpc() nakamaRpcFunc {
	return func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
		var data map[string]string

		if err := json.Unmarshal([]byte(payload), &data); err != nil {
			return "", errors.New("invalid payload")
		}

		challengedId := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
		challengerId := data["opponent_id"]
		matchId := "" // Gather match id by creating a new match

		// Check if opponentID is valid, befriended and whatever you like to validate...
		SendAcceptedNotifications(ctx, nk, logger, challengedId, challengerId, matchId)

		return "", nil
	}
}

Don’t pay attention to the usage of JSON instead of protobuf or missing validations, it’s just for demonstration.