7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NuxtでSocket.ioを使う

Last updated at Posted at 2024-08-28

はじめに

WebSocket使いたいけどわざわざバックエンドを別で立てるのめんどくさい時ありますよね。
そんな時はNuxt.jsかNext.jsあたりでフロントエンド開発と同じ感覚で開発したいものですが、
例によってNext.jsの記事はあってもNuxt.jsの記事はあんまりないので書こうと思いました。
※基本的にSocket.ioの公式ドキュメント通りですが、一部自分の追加した記述も載せます。

あとNuxt.jsで調べると出てくる下記モジュールの開発が止まっているのと、自分の環境で試したらうまく動かなかったのも理由です

対象読者

  • Vue/Nuxtを使っていてWebSocketやりたい人
  • Vue + バックエンドよりもNuxtだけでWebSocketやりたい人

実装

ライブラリ追加

Nuxt.jsのプロジェクトは作成できている前提で、まずはライブラリを追加してください。

npm install socket.io socket.io-client

nitro設定

現在nitro上において、WebSocketを利用した機能はまだExperimentalなので、下記のように書く必要があります。(今後は変わるかも。こちらのマージを待ちましょう)

nuxt.config.ts
export default defineNuxtConfig({
    ~~~他の設定~~~
    nitro: {
        experimental: {
            websocket: true
        },
    }
})

plugin作成

バックエンドフレームワーク内に配置することの多いSocket.ioの設定を、Nuxtではserver/pluginsに配置します。
Socket.ioの公式ドキュメントにはないですが、私はアクティブなデバイスの判別をしたかったのでタイムアウトしたデバイスの削除処理などを追加しています。

server/plugins/socket.io.ts
import type { NitroApp } from "nitropack";
import { Server as Engine } from "engine.io";
import { Server } from "socket.io";
import { defineEventHandler } from "h3";

export default defineNitroPlugin((nitroApp: NitroApp) => {
    const engine = new Engine();
    const io = new Server();

    io.bind(engine);

    const activeDevices = new Map()

    io.on("connection", (socket) => {
        console.log('Client connected')

        // ここで下記のように機能追加する
        socket.on('sample', (data) => {
            // 他のクライアントにブロードキャスト
            socket.broadcast.emit('sampleUpdate', data)
        })

     // アクティブなデバイスのリセット
        socket.on('disconnect', () => {
            console.log('Client disconnected')
            for (const [deviceId, device] of activeDevices.entries()) {
                if (device.socket === socket) {
                    activeDevices.delete(deviceId)
                    io.emit('deviceDisconnected', deviceId)
                }
            }
        })
    });

    // アクティブなデバイスの管理
    setInterval(() => {
        const now = Date.now()
        for (const [deviceId, device] of activeDevices.entries()) {
            if (now - device.lastUpdate > 10000) { // 10 seconds timeout
                activeDevices.delete(deviceId)
                io.emit('deviceDisconnected', deviceId)
            }
        }
    }, 5000)

    nitroApp.router.use("/socket.io/", defineEventHandler({
        handler(event) {
            engine.handleRequest(event.node.req, event.node.res);
            event._handled = true;
        },
        websocket: {
            open(peer) {
                const nodeContext = peer.ctx.node;
                const req = nodeContext.req;

                // @ts-expect-error private method
                engine.prepare(req);

                const rawSocket = nodeContext.req.socket;
                const websocket = nodeContext.ws;

                // @ts-expect-error private method
                engine.onWebSocket(req, rawSocket, websocket);
            }
        }
    }));
});

クライアント側

utilsにクライアント側共通で利用するSocketを作成します。

utils/socket/socket.ts
import { io } from "socket.io-client";

export const socket = io();

WebSocketを利用するコンポーネントは下記のように記述します。
公式ドキュメントだと.client.vueファイルで記述されていましたが、普通のvueファイルでもライフサイクルに気をつけていれば問題なく動くはずです。

components/socket-component.vue
<script setup lang="ts">
import {ref, watch} from "vue";
import {socket} from "~/utils/socket/socket";

const isConnected = ref(false);
const transport = ref("N/A");

if (socket.connected) {
  onConnect();
}

function onConnect() {
  isConnected.value = true;
  transport.value = socket.io.engine.transport.name;
  socket.io.engine.on("upgrade", (rawTransport) => {
    transport.value = rawTransport.name;
  });
}

function onDisconnect() {
  isConnected.value = false;
  transport.value = "N/A";
}

socket.on("connect", onConnect);
socket.on('sampleUpdate', (data: {id:string, lastUpdate: number}) => {
  // コンポーネント側の処理
})

socket.on('deviceDisconnected', (deviceId: string) => {
  // 接続が切断された時の処理
})
socket.on("disconnect", onDisconnect);

onBeforeUnmount(() => {
  socket.off("connect", onConnect);
  socket.off("disconnect", onDisconnect);
});
</script>

<template>
  <div>
    <p>Status: {{ isConnected ? "connected" : "disconnected" }}</p>
   <p>Transport: {{ transport }}</p>
 </div>
</template>

以上です。あとはいい感じにサービスへ取り込んでみてください!

参考文献

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?