connection works

This commit is contained in:
2025-11-27 17:16:07 +05:30
parent 4565c4b33a
commit 4d6511505a
2 changed files with 144 additions and 71 deletions

View File

@@ -19,8 +19,8 @@ export default function TicTacToe() {
joinMatchmaker, joinMatchmaker,
joinMatch, joinMatch,
onMatchData, onMatchData,
socket, onMatchmakerMatched,
sendMatchData sendMatchData,
} = useNakama(); } = useNakama();
// ------------------------------------------ // ------------------------------------------
@@ -42,15 +42,14 @@ export default function TicTacToe() {
} }
}); });
// When matchmaker finds a match onMatchmakerMatched(async (matched) => {
socket!.onmatchmakermatched = async (matched) => {
console.log("Matched:", matched); console.log("Matched:", matched);
const fullMatchId = matched.match_id; const fullMatchId = matched.match_id;
setMatchId(fullMatchId); setMatchId(fullMatchId);
await joinMatch(fullMatchId); await joinMatch(fullMatchId);
}; })
} }
// ------------------------------------------ // ------------------------------------------
@@ -77,7 +76,7 @@ export default function TicTacToe() {
{!matchId && ( {!matchId && (
<> <>
<input <input
placeholder="username/device ID" placeholder="username"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
/> />

View File

@@ -6,90 +6,186 @@ import {
MatchmakerTicket, MatchmakerTicket,
Match, Match,
MatchData, MatchData,
MatchmakerMatched,
} from "@heroiclabs/nakama-js"; } from "@heroiclabs/nakama-js";
import React, { createContext, useContext, useState } from "react";
import React, {
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
// ---------------------------------------------
// UNIQUE DEVICE ID FOR BROWSER
// ---------------------------------------------
function getOrCreateDeviceId(): string {
const key = "nakama.deviceId";
let id = localStorage.getItem(key);
if (!id) {
id = crypto.randomUUID();
localStorage.setItem(key, id);
}
return id;
}
// ---------------------------------------------
// CONTEXT TYPE
// ---------------------------------------------
export interface NakamaContextType { export interface NakamaContextType {
client: Client; client: Client;
socket: Socket | null; socket: Socket | null;
session: Session | null; session: Session | null;
loginOrRegister: (username: string) => Promise<Session>; loginOrRegister: (username: string) => Promise<Session>;
joinMatchmaker: (mode: string) => Promise<string>; joinMatchmaker: (mode: string) => Promise<string>;
leaveMatchmaker: (ticket: string) => Promise<void>; leaveMatchmaker: (ticket: string) => Promise<void>;
joinMatch: (matchId: string) => Promise<Match>; joinMatch: (matchId: string) => Promise<Match>;
sendMatchData: (matchId: string, opCode: number, data: object) => void; sendMatchData: (matchId: string, opCode: number, data: object) => void;
onMatchData: ( onMatchData: (
cb: (msg: { opCode: number; data: any; userId: string | null }) => void cb: (msg: { opCode: number; data: any; userId: string | null }) => void
) => void; ) => void;
onMatchmakerMatched: (cb: (result: MatchmakerMatched) => void) => void;
} }
export const NakamaContext = createContext<NakamaContextType | null>(null); export const NakamaContext = createContext<NakamaContextType | null>(null);
// ---------------------------------------------
// PROVIDER IMPLEMENTATION
// ---------------------------------------------
export function NakamaProvider({ children }: { children: React.ReactNode }) { export function NakamaProvider({ children }: { children: React.ReactNode }) {
const [client] = useState<Client>( // Use "defaultkey", "127.0.0.1", "7350" — match your given client
() => new Client( const [client] = useState(
"defaultkey", () =>
"127.0.0.1", new Client(
"7350" "defaultkey",
) "127.0.0.1",
"7350",
false, // use SSL?
)
); );
const [socket, setSocket] = useState<Socket | null>(null);
const [session, setSession] = useState<Session | null>(null); const [session, setSession] = useState<Session | null>(null);
const [socket, setSocket] = useState<Socket | null>(null);
// ----------------------- // ---------------------------------------------
// LOGIN / REGISTER // CALLBACK REGISTRIES (React-safe)
// ----------------------- // ---------------------------------------------
const matchDataCallbacks = useRef<
((msg: { opCode: number; data: any; userId: string | null }) => void)[]
>([]);
const matchmakerMatchedCallbacks = useRef<
((result: MatchmakerMatched) => void)[]
>([]);
// Register match data listener
function onMatchData(
cb: (msg: { opCode: number; data: any; userId: string | null }) => void
) {
matchDataCallbacks.current.push(cb);
}
// Register matchmaker matched listener
function onMatchmakerMatched(cb: (result: MatchmakerMatched) => void) {
matchmakerMatchedCallbacks.current.push(cb);
}
// ---------------------------------------------
// SETUP SOCKET EVENT LISTENERS WHEN SOCKET IS READY
// ---------------------------------------------
useEffect(() => {
if (!socket) return;
// MATCH DATA
socket.onmatchdata = (msg: MatchData) => {
const raw = msg.data;
let decoded: any;
try {
decoded = JSON.parse(new TextDecoder().decode(raw));
} catch {
decoded = raw;
}
matchDataCallbacks.current.forEach((cb) =>
cb({
opCode: msg.op_code,
data: decoded,
userId: msg.presence?.user_id || null,
})
);
};
// MATCHMAKER MATCHED
socket.onmatchmakermatched = (result) => {
matchmakerMatchedCallbacks.current.forEach((cb) => cb(result));
};
return () => {
// Cleanup (not strictly needed, but clean React practice)
socket.onmatchdata = undefined as any;
socket.onmatchmakermatched = undefined as any;
};
}, [socket]);
// ---------------------------------------------
// LOGIN OR REGISTER
// ---------------------------------------------
async function loginOrRegister(username: string): Promise<Session> { async function loginOrRegister(username: string): Promise<Session> {
try { try {
const s = await client.authenticateDevice(username, true); const deviceId = getOrCreateDeviceId();
// authenticateDevice(id, create, username)
const s = await client.authenticateDevice(deviceId, true, username);
setSession(s); setSession(s);
const newSocket = new DefaultSocket( const newSocket = client.createSocket(false, false);
client.host,
client.port,
false, // useSSL
false // verbose
);
await newSocket.connect(s, true); await newSocket.connect(s, true);
setSocket(newSocket); setSocket(newSocket);
console.log("[Nakama] Connected WS"); console.log("[Nakama] Connected via WebSocket");
return s; return s;
} catch (err) { } catch (err) {
console.error("[Nakama] Login error", err); console.error("[Nakama] Login error:", err);
throw err; throw err;
} }
} }
// ----------------------- // ---------------------------------------------
// MATCHMAKING // MATCHMAKING
// ----------------------- // ---------------------------------------------
async function joinMatchmaker(mode: string): Promise<string> { async function joinMatchmaker(mode: string): Promise<string> {
if (!socket) throw new Error("Socket not ready."); if (!socket) throw new Error("Socket not ready.");
const ticket = await socket.addMatchmaker( const ticket: MatchmakerTicket = await socket.addMatchmaker(
`+mode:"${mode}"`, // query `+mode:"${mode}"`, // query
2, // min players 2, // min count
2, // max players 2, // max count
{ mode } // string properties { mode } // stringProperties
); );
console.log("[Nakama] Matchmaker ticket:", ticket.ticket); console.log("[Nakama] Ticket:", ticket.ticket);
return ticket.ticket; return ticket.ticket;
} }
async function leaveMatchmaker(ticket: string): Promise<void> { async function leaveMatchmaker(ticket: string) {
if (socket) { if (socket) {
await socket.removeMatchmaker(ticket); await socket.removeMatchmaker(ticket);
} }
} }
// ----------------------- // ---------------------------------------------
// JOIN MATCH // JOIN MATCH
// ----------------------- // ---------------------------------------------
async function joinMatch(matchId: string): Promise<Match> { async function joinMatch(matchId: string): Promise<Match> {
if (!socket) throw new Error("Socket not connected."); if (!socket) throw new Error("Socket not connected.");
const match = await socket.joinMatch(matchId); const match = await socket.joinMatch(matchId);
@@ -97,41 +193,17 @@ export function NakamaProvider({ children }: { children: React.ReactNode }) {
return match; return match;
} }
// ----------------------- // ---------------------------------------------
// SEND MATCH DATA // SEND MATCH DATA
// ----------------------- // ---------------------------------------------
function sendMatchData(matchId: string, opCode: number, data: object = {}): void { function sendMatchData(matchId: string, opCode: number, data: object) {
if (!socket) return; if (!socket) return;
socket.sendMatchState(matchId, opCode, JSON.stringify(data)); socket.sendMatchState(matchId, opCode, JSON.stringify(data));
} }
// ----------------------- // ---------------------------------------------
// LISTENERS // PROVIDER VALUE
// ----------------------- // ---------------------------------------------
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 ( return (
<NakamaContext.Provider <NakamaContext.Provider
value={{ value={{
@@ -144,6 +216,7 @@ export function NakamaProvider({ children }: { children: React.ReactNode }) {
joinMatch, joinMatch,
sendMatchData, sendMatchData,
onMatchData, onMatchData,
onMatchmakerMatched,
}} }}
> >
{children} {children}
@@ -151,10 +224,11 @@ export function NakamaProvider({ children }: { children: React.ReactNode }) {
); );
} }
// ---------------------------------------------
// USE HOOK
// ---------------------------------------------
export function useNakama(): NakamaContextType { export function useNakama(): NakamaContextType {
const ctx = useContext(NakamaContext); const ctx = useContext(NakamaContext);
if (!ctx) { if (!ctx) throw new Error("useNakama must be inside a NakamaProvider");
throw new Error("useNakama must be used inside a NakamaProvider");
}
return ctx; return ctx;
} }