LoginSignup
0
1

More than 1 year has passed since last update.

Webアプリ間でWebSocket使ってデータをリアルタイムにやりとりするの作ってみたのでメモ

Posted at

概要

ちょっとしたデモを作る時など、PCやスマホ、IoTデバイス等の間でリアルタイムにデータをやりとりしたいときって結構あります。
そんなとき Firestore 使うと便利なんですけど、Internet につながらない環境という場合がけっこうありまして、そんなのときのためにローカル回線の中で簡易 Firestore 的なこと(データを更新したら各WebApp上のデータも自動でリアルタイムに更新されるというもの)を実現してみました。

作ったもの

同期するデータ(サーバ/クライアントで共有)

synchroData.ts

interface AllData {
  aaa: number
  bbb: string
  ccc: string[]
}

const allData: AllData = {
  aaa: 123,
  bbb: 'abc',
  ccc: [],
}

export default {}
export { allData, AllData }

WebSocketサーバ(node-ts)

package.json

{
  "name": "synchro-data",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "ts-node src/index.ts"
  },
  "license": "MIT",
  "devDependencies": {
    "typescript": "^4.5.4"
  },
  "dependencies": {
    "@types/lodash": "^4.14.178",
    "@types/node": "^17.0.8",
    "@types/ws": "^8.2.2",
    "lodash": "^4.17.21",
    "ts-node": "^10.4.0",
    "ws": "^8.4.0"
  }
}

index.ts

import { Server } from 'ws'
import { set } from 'lodash'
import { allData, AllData } from './synchroData'

const ws = new Server({ port: 5001 });

ws.on('connection', socket => {
  console.log('connected!')

  // 初期データ受け取り
  socket.send(JSON.stringify({
    allData,
  }))

  socket.on('message', msg => {
    console.log('receive: ' + msg);
    // 自身のデータ更新
    const received = JSON.parse(`${msg}`)
    if (received.method === 'set') {
      set(allData, received.path, received.data)
      console.log('allData', allData)
    }
    // クライアントにリレー
    ws.clients.forEach(client => {
      // console.log(`${msg}`);
      client.send(`${msg}`)
    })
  })

  socket.on('close', () => {
    console.log('good bye.');
  })
})

console.log('Start!')

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2021",
    "sourceMap": true,
    "types": [
      "node"
    ]
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

クライアント(nuxt3)

package.json

{
  "private": true,
  "scripts": {
    "dev": "nuxi dev",
    "build": "nuxi build",
    "start": "node .output/server/index.mjs"
  },
  "devDependencies": {
    "nuxt3": "latest"
  },
  "dependencies": {
    "@types/lodash": "^4.14.178",
    "lodash": "^4.17.21"
  }
}

nuxt.config.ts

import { defineNuxtConfig } from 'nuxt3'

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
  ssr: false,
})

plugins/SynchroDataPlugin.ts

import { reactive, ref } from 'vue'
import { defineNuxtPlugin } from '#app'
import { set as lodashSet } from 'lodash'
import { allData, AllData } from '~/plugins/synchroData'

interface SynchroDataJournal {
  method: 'set' | 'append'
  path: string
  data: any
}

const initSynchroData = <SynchroData extends object> (defaultData: SynchroData) => {
  type ResOfAllData = { allData: SynchroData }

  const isResOfAllData = <SynchroData> (res: any): res is ResOfAllData => {
    return !!res['allData']
  }

  const synchroData = reactive<SynchroData>(defaultData)
  const synchroDataStatus = ref('')

  const mutateSynchroData = (journal: SynchroDataJournal) => {
    if (journal.method === 'set') {
      lodashSet(synchroData, journal.path, journal.data)
      // console.log('allData', allData)
    }
  }

  const ws = new WebSocket('ws://localhost:5001')
  ws.addEventListener('open', (e) => {
    synchroDataStatus.value = 'Socket Success'
  })
  ws.addEventListener('message', (e) => {
    synchroDataStatus.value = `received: ${e.data}`
    const received = JSON.parse(`${e.data}`) as ResOfAllData | SynchroDataJournal
    if (isResOfAllData(received)) {
      Object.assign(synchroData, received.allData)
    } else {
      mutateSynchroData(received)
    }
  })

  const synchroDataEmit = (journal: SynchroDataJournal) => {
    ws.send(JSON.stringify(journal))
  }

  return {
    provide: {
      synchroData,
      synchroDataStatus,
      synchroDataEmit,
    }
  }
}

export default defineNuxtPlugin(nuxtApp => initSynchroData<AllData>(allData))

pages/index.vue

<template>
  <div>
    <div>{{ $synchroDataStatus }}</div>
    <div>{{ $synchroData }}</div>
    <button @click="dataChange">
      dataChange
    </button>
  </div>
</template>

<script setup lang="ts">
const { $synchroData, $synchroDataStatus, $synchroDataEmit } = useNuxtApp()

const dataChange = () => {
  $synchroDataEmit({
    method: 'set',
    path: 'aaa',
    data: $synchroData.aaa + 1,
  })
}
</script>

結果

これで $synchroData には常に最新のデータがリアクティブに入っている状態になる。

0
1
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
0
1