added nakama provider using nakama package

This commit is contained in:
2025-11-27 16:51:06 +05:30
parent c6c9d10476
commit 4565c4b33a
6 changed files with 331 additions and 50 deletions

View File

@@ -0,0 +1,160 @@
import {
Client,
DefaultSocket,
Session,
Socket,
MatchmakerTicket,
Match,
MatchData,
} from "@heroiclabs/nakama-js";
import React, { createContext, useContext, useState } from "react";
export interface NakamaContextType {
client: Client;
socket: Socket | null;
session: Session | null;
loginOrRegister: (username: string) => Promise<Session>;
joinMatchmaker: (mode: string) => Promise<string>;
leaveMatchmaker: (ticket: string) => Promise<void>;
joinMatch: (matchId: string) => Promise<Match>;
sendMatchData: (matchId: string, opCode: number, data: object) => void;
onMatchData: (
cb: (msg: { opCode: number; data: any; userId: string | null }) => void
) => void;
}
export const NakamaContext = createContext<NakamaContextType | null>(null);
export function NakamaProvider({ children }: { children: React.ReactNode }) {
const [client] = useState<Client>(
() => new Client(
"defaultkey",
"127.0.0.1",
"7350"
)
);
const [socket, setSocket] = useState<Socket | null>(null);
const [session, setSession] = useState<Session | null>(null);
// -----------------------
// LOGIN / REGISTER
// -----------------------
async function loginOrRegister(username: string): Promise<Session> {
try {
const s = await client.authenticateDevice(username, true);
setSession(s);
const newSocket = new DefaultSocket(
client.host,
client.port,
false, // useSSL
false // verbose
);
await newSocket.connect(s, true);
setSocket(newSocket);
console.log("[Nakama] Connected WS");
return s;
} catch (err) {
console.error("[Nakama] Login error", err);
throw err;
}
}
// -----------------------
// MATCHMAKING
// -----------------------
async function joinMatchmaker(mode: string): Promise<string> {
if (!socket) throw new Error("Socket not ready.");
const ticket = await socket.addMatchmaker(
`+mode:"${mode}"`, // query
2, // min players
2, // max players
{ mode } // string properties
);
console.log("[Nakama] Matchmaker ticket:", ticket.ticket);
return ticket.ticket;
}
async function leaveMatchmaker(ticket: string): Promise<void> {
if (socket) {
await socket.removeMatchmaker(ticket);
}
}
// -----------------------
// JOIN MATCH
// -----------------------
async function joinMatch(matchId: string): Promise<Match> {
if (!socket) throw new Error("Socket not connected.");
const match = await socket.joinMatch(matchId);
console.log("[Nakama] Joined match:", matchId);
return match;
}
// -----------------------
// SEND MATCH DATA
// -----------------------
function sendMatchData(matchId: string, opCode: number, data: object = {}): void {
if (!socket) return;
socket.sendMatchState(matchId, opCode, JSON.stringify(data));
}
// -----------------------
// LISTENERS
// -----------------------
function onMatchData(
cb: (msg: { opCode: number; data: any; userId: string | null }) => void
) {
if (!socket) return;
socket.onmatchdata = (msg: MatchData) => {
const raw = msg.data;
let decoded: any = null;
try {
decoded = JSON.parse(new TextDecoder().decode(raw));
} catch {
decoded = raw; // fallback to raw binary
}
cb({
opCode: msg.op_code,
data: decoded,
userId: msg.presence?.user_id ?? null,
});
};
}
return (
<NakamaContext.Provider
value={{
client,
socket,
session,
loginOrRegister,
joinMatchmaker,
leaveMatchmaker,
joinMatch,
sendMatchData,
onMatchData,
}}
>
{children}
</NakamaContext.Provider>
);
}
export function useNakama(): NakamaContextType {
const ctx = useContext(NakamaContext);
if (!ctx) {
throw new Error("useNakama must be used inside a NakamaProvider");
}
return ctx;
}