CreateMatchAsync Query

When we create a private game using CreateMatchAsync var match = await _socket.CreateMatchAsync(roomName); does it create server authoritative multiplayer room or the client relayed multilayer?

For online_random mode, I have already registered the match
initializer.registerMatch(online_random, {
matchInit,
matchJoinAttempt,
matchJoin,
matchLeave,
matchLoop,
matchSignal,
matchTerminate
});
Not sure if I need to register one more for private game which I want to create using creatematchasync client API.

Can someone share server authoritative private room create and join code!.

Hello @Sathyaraj,

If you create a match from the client with _socket.CreateMatchAsync it will be a relayed match, to be able to create an authoritative match with the your registered custom match handler functions you’ll need to either expose match creation via an RPC that calls the server runtime nk.matchCreate function.

You can see an example in our project template.

Thanks for the reply @sesposito I created the match in server stored the short key as mapping in storage and use that short key to get the actual roomID and join the game. I am deleting old ids when creating the game. Sharing the code snippet here. Please advice if this can be improved

interface RpcCreateMatchRequest {
    maxPlayers: 0
    stockPileCards: 0
}

interface RpcJoinMatchRequest {
    roomCode: ""
}

function rpcCreateMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {

    logger.info("rpcCreateMatch.............................. " + payload);

    //In this line, you're type casting an empty object to RpcCreateMatchRequest. It doesn't actually create an instance of RpcCreateMatchRequest, it just treats the empty object as if it were an instance of RpcCreateMatchRequest.
    let request = {} as RpcCreateMatchRequest;

    try {
        request = JSON.parse(payload);
        logger.info("rpcCreateMatch request.............................. maxPlayers " + request.maxPlayers + " stockPileCards " + request.stockPileCards);
    } catch (error) {
        logger.error('........Error parsing json message: %q', error);
        throw error;
    }
    let matchId = nk.matchCreate(online_private, {
        minPlayers: 2,
        maxPlayers: request.maxPlayers
    });

    // Generate a short, human-readable code for the match.
    let shortCode = generateShortCode();

    deleteOldGames(context, logger, nk);
    // Write the short code and matchId to Nakama storage.
    nk.storageWrite([
        {
            collection: "CustomMatchIds",
            key: shortCode,
            userId: SYSTEM_USER_ID,
            value: { MatchId: matchId, CreatedAt: Date.now() },
            permissionRead: 2,
            permissionWrite: 1
        }
    ]);
    logger.info("rpcCreateMatch matchId.............................. " + matchId + " shortCode " + shortCode + " userId " + SYSTEM_USER_ID);

    return JSON.stringify({shortCode});
}

function generateShortCode(): string {
    let code = Math.floor(1000 + Math.random() * 9000); // This will generate a random number between 1000 and 9999.
    return code.toString();
}

function rpcGetShortRoomCode(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {

    logger.info("rpcGetShortRoomCode.............................. " + payload + " userId " + context.userId);
    let matchId;

    //In this line, you're type casting an empty object to RpcJoinMatchRequest. It doesn't actually create an instance of RpcJoinMatchRequest, it just treats the empty object as if it were an instance of RpcJoinMatchRequest.
    let request = {} as RpcJoinMatchRequest;

    try {
        request = JSON.parse(payload);
        logger.info("rpcGetShortRoomCode --> request " + request.roomCode);

        let matches = nk.storageList(SYSTEM_USER_ID, "CustomMatchIds", 100);

        // Check if matches or matches.objects is defined.
        if (!matches || !matches.objects)
            throw new Error("rpcGetShortRoomCode --> No matches found.");

        if (matches.objects.length === 0)
            logger.info("rpcGetShortRoomCode --> 'matches.objects' is empty.");

        logger.info("rpcGetShortRoomCode --> The length of matches.objects is:" + matches.objects.length);

        let matchObject = matches.objects.filter(object => object.key === request.roomCode)[0];

        logger.info("rpcGetShortRoomCode --> matchObject: " + JSON.stringify(matchObject));
        if (!matchObject || !matchObject.value || !matchObject.value.MatchId) {
            logger.info("rpcGetShortRoomCode --> Custom match ID not found.");
            throw new Error("rpcGetShortRoomCode --> Custom match ID not found.");
        }

        matchId = matchObject.value.MatchId;
        logger.info("rpcGetShortRoomCode --> request.............................. short roomCode " + request.roomCode + " actual matchId " + matchId);

    } catch (error) {
        logger.error('........rpcGetShortRoomCode --> %q', error);
        throw error;
    }

    return JSON.stringify({matchId});
}

function deleteOldGames(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
    const TWO_DAYS_MILLISECONDS = 2 * 24 * 60 * 60 * 1000
    const currentTime = (new Date()).getTime();

    let objectList = nk.storageList(context.userId, "CustomMatchIds", 100);

    // Check if matches or matches.objects is defined.
    if (!objectList || !objectList.objects)
        throw new Error("deleteOldGames --> No old matches found.");

    logger.info("deleteOldGames --> objectList.............................. " + objectList.objects.length);

    for(let object of objectList.objects) {
        if(object.value && object.value.CreatedAt && (currentTime - object.value.CreatedAt > TWO_DAYS_MILLISECONDS)) {
            logger.info("deleteOldGames --> deleting object.............................. " + object.value + " object.key " + object.key);
            nk.storageDelete([{
                collection: object.collection,
                key: object.key,
                userId: SYSTEM_USER_ID
            }]);
        }
    }
}

And when registering the match I am using the same callbacks. Is it fine or do I need to maintain separate callbacks for different game modes?

function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    logger.info("Hello World!..............................");

    initializer.registerRpc("rpc_online_private", rpcCreateMatch);
    initializer.registerRpc("rpc_get_short_room_code", rpcGetShortRoomCode);

    initializer.registerMatch(online_random, {
        matchInit,
        matchJoinAttempt,
        matchJoin,
        matchLeave,
        matchLoop,
        matchSignal,
        matchTerminate
    });

    initializer.registerMatch(online_private, {
        matchInit,
        matchJoinAttempt,
        matchJoin,
        matchLeave,
        matchLoop,
        matchSignal,
        matchTerminate
    });

    logger.info('JavaScript logic loaded.');
}