added nakama provider using nakama package
This commit is contained in:
160
src/tictactoe/providers/NakamaProvider.tsx
Normal file
160
src/tictactoe/providers/NakamaProvider.tsx
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user