Typescript module error: "TypeError: matchInit value not a valid function"

Hey everyone! I’m really new to Nakama and tried to write a TypeScript module for an authoritative match. I don’t get any compilation errors when compiling with npx tsc, but when starting the nakama server using docker-compose up, I get the following error message:

nakama_1       | {"level":"error","ts":"2022-05-11T12:55:10.917Z","caller":"server/runtime_javascript.go:1521","msg":"Failed to eval JavaScript modules.","error":"TypeError: matchInit value not a valid function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}
nakama_1       | {"level":"error","ts":"2022-05-11T12:55:10.917Z","caller":"server/runtime.go:597","msg":"Error initialising JavaScript runtime provider","error":"TypeError: matchInit value not a valid function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}
nakama_1       | {"level":"fatal","ts":"2022-05-11T12:55:10.917Z","caller":"main.go:146","msg":"Failed initializing runtime modules","error":"TypeError: matchInit value not a valid function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}
nakama_nakama_1 exited with code 1

after which Nakama exits.

I’m using Nakama Server version 3.9.0 with docker-compose on Ubuntu 20.04.4 LTS.
The version line in the docker-compose.yml looks like this:

nakama:
    image: heroiclabs/nakama:3.9.0

I would greatly appreciate any help, and if anyone knows what I might be doing wrong, please let me know :slight_smile:

Below is my typescript code (before compiling):

let InitModule : nkruntime.InitModule = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
        logger.debug('Module initialized.');

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

const matchInit = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}) : {state: nkruntime.MatchState, tickRate: number, label: string} => {
        logger.debug('Match initialized.');
        return {
                state: { },
                tickRate: 1,
                label: ''
        };
};

const matchJoinAttempt = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence, metadata : {[key : string]: any}) : {state: nkruntime.MatchState, accept: boolean, rejectMessage?: string | undefined} | null => {
        return {
                state,
                accept: true
        };
};

const matchJoin = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : {state: nkruntime.MatchState} | null => {
        return {state};
};

const matchLeave = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : {state: nkruntime.MatchState} | null => {
        return {state};
};

const matchLoop = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : {state: nkruntime.MatchState} | null => {
        return {state};
};

const matchTerminate = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : {state: nkruntime.MatchState} | null => {
        return {state};
};

const matchSignal = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : {state: nkruntime.MatchState, data?: string} | null => {
        logger.debug('Lobby match signal recieved: ' + data);
        return {state, data};
};

Hello @Coxcopi, the Nakama JS runtime only supports ES5 JS at the moment, so anonymous arrow function declaration is unsupported, please use const foo = function() {} syntax to register the match handlers and it should resolve your issue.

Hope this helps.

Hey @sesposito, thanks, this seems to have fixed the error I was getting. However, I’m now getting another error:

nakama_1       | {"level":"error","ts":"2022-05-11T15:59:21.980Z","caller":"server/runtime_javascript.go:1521","msg":"Failed to eval JavaScript modules.","error":"GoError: failed to find InitModule function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}
nakama_1       | {"level":"error","ts":"2022-05-11T15:59:21.980Z","caller":"server/runtime.go:597","msg":"Error initialising JavaScript runtime provider","error":"GoError: failed to find InitModule function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}
nakama_1       | {"level":"fatal","ts":"2022-05-11T15:59:21.980Z","caller":"main.go:146","msg":"Failed initializing runtime modules","error":"GoError: failed to find InitModule function\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat InitModule (index.js:13:9(25))\n"}

This is how my InitModule and the rest of the script look like now:

// Module init
let InitModule : nkruntime.InitModule = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
        logger.debug('Module initialized.');
        // Reigster match
        initializer.registerMatch('lobby', {
                matchInit,
                matchJoinAttempt,
                matchJoin,
                matchLoop,
                matchLeave,
                matchTerminate,
                matchSignal
        });
};

// Match initialization function (runs once when the match is created either via rpc or through the matchmaker)
let matchInit: nkruntime.MatchInitFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}) : {state: nkruntime.MatchState, tickRate: number, label: string} {
        logger.debug('Match initialized.');
        return {
                state: { },
                tickRate: 1,
                label: ''
        };
};

// When a player tries to join
const matchJoinAttempt: nkruntime.MatchJoinAttemptFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence, metadata : {[key : string]: any}) : {state: nkruntime.MatchState, ac>        return {
                state,
                accept: true
        };
};

// When one (or multiple) player(s) actually join(s)
const matchJoin: nkruntime.MatchJoinFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : {state: nkruntime.MatchState} | null {
        return {state};
};

// When a player leaves
const matchLeave: nkruntime.MatchLeaveFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : {state: nkruntime.MatchState} | null {
        return {state};
};

// Runs every tick
const matchLoop: nkruntime.MatchLoopFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : {state: nkruntime.MatchState} | null {
        return {state};
};

// Runs when the match gets terminated
const matchTerminate: nkruntime.MatchTerminateFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : {state: nkruntime.MatchState} | null {
        return {state};
};

const matchSignal: nkruntime.MatchSignalFunction<nkruntime.MatchState> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : {state: nkruntime.MatchState, data?: string} | null {
        logger.debug('Lobby match signal recieved: ' + data);
        return {state, data};
};

Thanks.

The above script has syntax errors around the matchJoinAttempt function, these should be caught by the TypeScript compiler, after fixing them, I managed to successfully load the script on Nakama 3.11.0, so if after fixing them the issue persists, please upgrade to the latest version of the server.

Updating both the docker files and the npm dependencies fixed it, thank you so much!

1 Like