Gone are the days when you’d have to configure web sockets for days and night before you can actually build the logic and multiplayers activity in your project / game.
In this blog we will see how easy it is to implement Supabase realtime. Here’s a summary of what we will make:
- Setup a room
- Subscribe to the room
- Alert current users when someone joins the room
- Track total users with supabase presence
- Alert current users when someone leaves
I assume you already do have project with authentication and supabase integrated and installed in it. So we will just focus on what matters.
1. Setup a room
// import what's necessary
export default function GameBoard({ user, gameId }) {
const supabase = createClient();
const userId = user.id; /* Id of the current logged in user */
const room = useRef(null);
const id = gameId; /* unique game id shareable to other users */
const [users, setUsers] = useState([]);
useEffect(() => {
const gameRoom = supabase.channel(`game:${gameId}`, {
config: {
broadcast: {
self: true /* recommended */,
},
presence: {
key: userId /* will be used to recognize user later */,
},
},
});
room.current = gameRoom;
// cleanup
return async () => {
if (room.current) {
await supabase.removeChannel(room.current);
}
};
}, []);
return <div>Hello world from my game room.</div>;
}
Now that we have a room in place we can subscribe the users to it.
2. Subscribe the users to the room
export default function GameBoard({ user, gameId }) {
const supabase = createClient();
const userId = user.id; /* Id of the current logged in user */
const room = useRef(null);
const id = gameId; /* unique game id shareable to other users */
const [users, setUsers] = useState([]);
useEffect(() => {
const gameRoom = supabase.channel(`game:${gameId}`, {
config: {
broadcast: {
self: true /* recommended */,
},
presence: {
key: userId /* will be used to recognize user later */,
},
},
});
room.current = gameRoom;
room.current.subscribe(async (status) => {
if (status !== "SUBSCRIBED") {
return null;
}
// track the presence of the user once subscribed to the channel
// this is necessary to keep a list of the users when they join
await room.current.track({
user: {
...user, // whatever you pass here will be accessible to the presence
},
});
});
// cleanup
return async () => {
if (room.current) {
await supabase.removeChannel(room.current);
}
};
}, []);
return <div>Hello world from my game room.</div>;
}
3. Alert current users when someone joins
export default function GameBoard({ user, gameId }) {
const supabase = createClient();
const userId = user.id; /* Id of the current logged in user */
const room = useRef(null);
const id = gameId; /* unique game id shareable to other users */
const [users, setUsers] = useState([]);
useEffect(() => {
const gameRoom = supabase.channel(`game:${gameId}`, {
config: {
broadcast: {
self: true /* recommended */,
},
presence: {
key: userId /* will be used to recognize user later */,
},
},
});
room.current = gameRoom;
room.current.subscribe(async (status) => {
if (status !== "SUBSCRIBED") {
return null;
}
// track the presence of the user once subscribed to the channel
await room.current.track({
user: {
...user, // whatever you pass here will be accessible to the presence
},
});
// send an event whenever a user joins the room with their information
room.current.send({
type: "broadcast",
event: "newUserJoined",
payload: {
userId,
user,
},
});
});
// listen to the newUserJoined event
room.current.on("newUserJoined", ({ payload }) => {
const name = payload.user.name;
const id = payload.userId; // this id will be the id of the newly joined user
if (id !== userId) {
// alert everyone excepts the newly joined user
// use a toast library or any other way to alert the user
toast(`${name} has joined the room!`);
}
});
// cleanup
return async () => {
if (room.current) {
await supabase.removeChannel(room.current);
}
};
}, []);
return <div>Hello world from my game room.</div>;
}
We can now also use supabase presence to track the total users.
export default function GameBoard({ user, gameId }) {
const supabase = createClient();
const userId = user.id; /* Id of the current logged in user */
const room = useRef(null);
const id = gameId; /* unique game id shareable to other users */
const [users, setUsers] = useState([]);
useEffect(() => {
const gameRoom = supabase.channel(`game:${gameId}`, {
config: {
broadcast: {
self: true /* recommended */,
},
presence: {
key: userId /* will be used to recognize user later */,
},
},
});
room.current = gameRoom;
room.current.subscribe(async (status) => {
if (status !== "SUBSCRIBED") {
return null;
}
// track the presence of the user once subscribed to the channel
await room.current.track({
user: {
...user, // whatever you pass here will be accessible to the presence
},
});
// send an event whenever a user joins the room with their information
room.current.send({
type: "broadcast",
event: "newUserJoined",
payload: {
userId,
user,
},
});
});
// listen to the newUserJoined event
room.current.on("newUserJoined", ({ payload }) => {
const name = payload.user.name;
const id = payload.userId; // this id will be the id of the newly joined user
if (id !== userId) {
// alert everyone excepts the newly joined user
// use a toast library or any other way to alert the user
toast(`${name} has joined the room!`);
}
});
// sync the users in the room
room.current.on("presence", { event: "sync" }, () => {
const newState = room.current.presenceState();
const values = Object.values(newState);
const users = values.map((v) => ({ ...v[0].user }));
setUsers(users); // this will set the total users in the room to the users state
});
// cleanup
return async () => {
if (room.current) {
await supabase.removeChannel(room.current);
}
};
}, []);
return <div>Hello world from my game room.</div>;
}
4. Alert current users when someone leaves
export default function GameBoard({ user, gameId }) {
const supabase = createClient();
const userId = user.id; /* Id of the current logged in user */
const room = useRef(null);
const id = gameId; /* unique game id shareable to other users */
const [users, setUsers] = useState([]);
useEffect(() => {
const gameRoom = supabase.channel(`game:${gameId}`, {
config: {
broadcast: {
self: true /* recommended */,
},
presence: {
key: userId /* will be used to recognize user later */,
},
},
});
room.current = gameRoom;
room.current.subscribe(async (status) => {
if (status !== "SUBSCRIBED") {
return null;
}
// track the presence of the user once subscribed to the channel
await room.current.track({
user: {
...user, // whatever you pass here will be accessible to the presence
},
});
// send an event whenever a user joins the room with their information
room.current.send({
type: "broadcast",
event: "newUserJoined",
payload: {
userId,
user,
},
});
});
// listen to the newUserJoined event
room.current.on("newUserJoined", ({ payload }) => {
const name = payload.user.name;
const id = payload.userId; // this id will be the id of the newly joined user
if (id !== userId) {
// alert everyone excepts the newly joined user
// use a toast library or any other way to alert the user
toast(`${name} has joined the room!`);
}
});
// sync the users in the room
room.current
.on("presence", { event: "sync" }, () => {
const newState = room.current.presenceState();
const values = Object.values(newState);
const users = values.map((v) => ({ ...v[0].user }));
setUsers(users); // this will set the total users in the room to the users state
})
.on("presence", { event: "leave" }, ({ key }) => {
// key is the userId of the user who left the room
// get the user who left the room
const user = users.find((u) => u.id === key);
toast(`${user.name} has left the room!`);
});
// cleanup
return async () => {
if (room.current) {
await supabase.removeChannel(room.current);
}
};
}, []);
return (
<div>
Hello world from my game room. <b>{users.length}</b> users are in the
room.
</div>
);
}
I really hope this helps you how to implement supabase realtime in your next project!
Good luck :)