DEV Community

Cover image for Membuat Tombol 'Play' Sederhana dengan WebSocket dan Honojs
Mashofa
Mashofa

Posted on

Membuat Tombol 'Play' Sederhana dengan WebSocket dan Honojs

Pada kesempatan ini saya ingin membagikan perjalanan saya menyelami Honojs dengan memanfaatkan websocket untuk komunikasi realtime.

"Play"

Saya sangat menyukai permainan, terutama jenis permainan kompetitif. Salah satu momen yang menarik dalam permainan semacam ini adalah ketika para pemain memasuki sebuah ruangan sebelum memasuki medan kompetisi. Di ruangan tersebut, pemain akan menunggu sampai seluruh tim siap untuk bertanding. Proses ini memerlukan komunikasi realtime antara pemain, di mana teknologi seperti WebSocket dan Honojs dapat digunakan untuk menghubungkan mereka dalam sesi permainan, memastikan bahwa setiap pemain menerima pembaruan status secara langsung.

Alur

Terinspirasi dari latar belakang diatas, maka saya membuat sebuah 2 halaman yaitu HomePage dan RoomPage.
...
Pada halaman HomePage user bisa membuat sebuah room dengan menekan tombol "Buat room", dan juga user bisa masuk ke sebuah room dengan memasukan nama room pada input text lalu tekan "Join"

Lalu pada halaman RoomPage terdapat tombol "Play" yang ketika semua user sudah menekannya maka ada pesan bahwa semua pemain sudah siap.

Pembuatan

file index.ts

import { Hono } from "hono";
import { HomePage } from "./views/Home";
import { jsxRenderer } from "hono/jsx-renderer";
import { RoomPage } from "./views/Room";
import { serveStatic } from "hono/bun";
import { cors } from "hono/cors";
// import { poweredBy } from 'hono/powered-by';

import room, { websocket } from './room.ws'

const app = new Hono();
app.use(
  "/*",
  cors({
    origin: ["http://localhost", "https://localhost"],
    allowMethods: ["GET", "POST"],
    exposeHeaders: ["Content-Type"],
    maxAge: 300,
  })
);

app.use(jsxRenderer());

app.use(
  "/js/*",
  serveStatic({
    root: "./src/public",
    onNotFound: (path, c) => {
      console.log(`${path} is not found, you access ${c.req.path}`);
    },
  })
);

app.get("/", (c) => {
  return c.html(<HomePage />);
});
app.get("/play/:name", (c) => {
  const name = c.req.param("name");
  return c.html(<RoomPage roomName={name} />);
});

app.route("/ws", room);

export default {
  fetch: app.fetch,
  websocket
};

Enter fullscreen mode Exit fullscreen mode

file room

import { Hono } from "hono";
import { createBunWebSocket } from "hono/bun";
import type { ServerWebSocket } from "bun";
import { WSContext } from "hono/ws";

const room = new Hono();

const { upgradeWebSocket, websocket } = createBunWebSocket<ServerWebSocket>();

type User = {
  id: string | number;
  socket: WSContext<ServerWebSocket<undefined>>;
  isUserReady: boolean;
};

const roomUser: Record<string, User[]> = {};

let roomName: string = "";
let userId: string = "";

room.get(
  "/play",
  upgradeWebSocket((c) => {
    // userId++;

    return {
      onOpen(evt, ws) {
        console.log("Connected ");
        ws.send(JSON.stringify({ message: "Hello from server !" }));
      },

      onMessage(evt, ws) {
        console.log(evt.data);
        let dataParse: any;

        try {
          dataParse = JSON.parse(evt?.data.toString());
        } catch (error) {
          ws.send("Invalid JSON");
          return;
        }

        dataParse = JSON.parse(evt.data.toString());
        roomName = dataParse.roomName;
        userId = dataParse.userId;

        if (!roomUser[roomName]) {
          roomUser[roomName] = [];
        }

        const thisUser = roomUser[roomName].find((u) => u.id == userId);

        if (!thisUser) {
          roomUser[roomName].push({
            id: userId,
            socket: ws,
            isUserReady: false,
          });
        }

        // console.log(thisUser);
        console.log(roomUser);

        if (dataParse.type === "STATUS_USER") {
          roomName = dataParse.roomName;

          if (!roomName) return console.warn("Room name not found !!");

          if(thisUser) thisUser.isUserReady = dataParse.isUserReady;
        console.warn(thisUser);

          if (roomUser[roomName].every((u) => u.isUserReady)) {
            roomUser[roomName].forEach((u) => {
              u.socket.send(
                JSON.stringify({
                  type: "PLAY",
                  message: "Everyone is ready",
                })
              );
            });
          } else {
            roomUser[roomName].forEach((u) => {
              u.socket.send(
                JSON.stringify({
                  type: "PLAY",
                  message: "Waiting for " + roomUser[roomName].filter(u => !u.isUserReady).length + " more",
                })
              );
            });
          }
          console.warn(roomUser[roomName]);

        }
      },

      onClose(evt, ws) {
        if (roomName && roomUser[roomName]) {
          roomUser[roomName] = roomUser[roomName].filter(
            (u) => u.id !== userId
          );
        }
        // console.log(userId);
        // console.log(roomUser);
      },
      onError(evt, ws) {},
    };
  })
);

export default room;
export { websocket };
Enter fullscreen mode Exit fullscreen mode

file Home.tsx

import { Layout } from "../components/Layout";
import { InputText } from "../components/InputText";

export const HomePage = () => {
  return (
    <Layout text="HONO JSX">
      <main class={`h-[90%] flex flex-col gap-[50px] max-w-[500px]`}>
        <h1 class={`text-center`}>Bermain bersama dengan HonoJS</h1>
        <div
          class={`p-4 bg-slate-200 rounded-md border border-stone-200 border-b-[2px] border-solid`}
        >
          <div class={`flex gap-2 items-center mb-4`}>
            <InputText
              type="text"
              id="inputRoom"
              placeholder="Masukan nama room"
              name="inputRoom"
              className="w-full bg-slate-50 border border-solid border-slate-300 px-2 py-3 rounded-lg"
            />
            {/* <input class={`w-full`} type="text" /> */}
            <Button id="join" label="Join" size="small" variant="secondary" />
          </div>
          <div className="w-full border-0 border-b-[2px] border-solid border-slate-300 mb-4"></div>
          <Button
            size="small"
            variant="primary"
            label="Buat Room"
            id="clickIni"
            fullWidth
          />
        </div>
      </main>
      <Footer></Footer>
      {html`<script src="/js/script.js" defer></script>`}
    </Layout>
  );
};

export const Footer = () => html`
  <footer class="py-3 bg-red-200 text-center rounded-4 w-full">
    <p>2025 mencoba <a href="https://github.com/mbank14">Hono</a></p>
  </footer>
`;
Enter fullscreen mode Exit fullscreen mode

untuk lebih lengkapnya kalian bisa kunjungi Repo ini

Apa yang saya dapat pelajari

Saya mempelajari bagaimana memanfaatkan JSX diHonoJs untuk membuat Component dan Layout, bagaimana cara mengimport static file dan tentunya membuat websocket.

Tantangan selanjutnya

Tantangan selanjutnya ini diambil dari beberapa saja kekuranganya seperti saya ingin mencoba menggunakan client side dengen memanfaatkan Vite, membuat batasan user yang bergabung, dan membuat efisiensi dalam alur websocket yang saya buat.

tulisan ini adalah jurnal perjalanan saya, jika kalian memiliki masukan ataupun kritik silahkan tulis dikomentar.

Top comments (0)