點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Vue-socket.io 實現 Broadcast

Sam Xiao's Avatar 2019-07-18

Broadcast 過去在 Web 屬夢寐以求的功能,但透過 Vue-socket.io ,可以很輕易實現。

Version

macOS Mojave 10.14.5
WebStorm 2019.1.3
Node 12.4
Express 4.17.1
Vue 2.6.10
Vue-socket.io 3.0.7

Chrome

broadcast000

Server

$ yarn add body-parser

安裝 body-parser module,為了能使用 req.body 讀取 POST 上傳內容。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Socket.io</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
  <script>
    let sendMessage = () => {
      axios.post('/notification', {
        message: document.getElementById('message').value,
        color: document.querySelector('input[name="color"]:checked').value
      });
    };
  </script>
</head>
<body>
  <div>
    <label>Message</label>
    <input type="text" name="message" id="message"><br/>
    <input type="radio" name="color" value="success" checked>Success <br/>
    <input type="radio" name="color" value="info">Info <br/>
    <input type="radio" name="color" value="error">Error <br/>

    <button onclick="sendMessage(); return false">Send Message</button>
  </div>
</body>
</html>

19 行

<input type="text" name="message" id="message"><br/>
<input type="radio" name="color" value="success" checked>Success <br/>
<input type="radio" name="color" value="info">Info <br/>
<input type="radio" name="color" value="error">Error <br/>

<button onclick="sendMessage(); return false">Send Message</button>

由 user 選擇 message color:共有 SuccessInfoError

第 8 行

let sendMessage = () => {
  axios.post('/notification', {
    message: document.getElementById('message').value,
    color: document.querySelector('input[name="color"]:checked').value
  });
};

將 message 與 color 透過 POST 送到 /notification

server.js

let dotenv = require('dotenv').config({
  path: '.env'
});

let express = require('express');
let path = require('path');
let bodyParser = require('body-parser');
let socket = require('./socket');

let app = express();
let port = process.env.PORT || 8500;
let http = require('http').Server(app);

app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());

let server = socket.initialize(http);
http.listen(port);

app.post('/notification', (req, res) => {
  console.log(`Message received: ${ req.body.message }`);
  server.emit('popupNotification', {
    message: req.body.message,
    color: req.body.color
  });
  res.send();
});

let heartbeat = () => {
  let pulse = Math.ceil(Math.random() * (160 - 60) + 60);
  console.log(`Heartbeat: ${ pulse }`);
  return pulse;
};

let emitPulseEvent = () => server.emit('pulse', heartbeat());

setInterval(emitPulseEvent, 1000);

console.log('server started');
console.log(`running in ${ process.env.NODE_ENV }`);
console.log(`running on port ${ port }`);

15 行

app.use(bodyParser.json());

使用 bodyParser

20 行

app.post('/notification', (req, res) => {
  console.log(`Message received: ${ req.body.message }`);
  server.emit('popupNotification', {
    message: req.body.message,
    color: req.body.color
  });
  res.send();
});

新設定 /notification 為 POST,使用 req.body 讀取 POST 上傳資料。

將 POST 資料使用 server.emit() 對 client 發出 popupNotification event。

Client

App.vue

<template>
  <v-app>
    <v-toolbar dark fixed app>
      <v-toolbar-title>Vue with Socket.io</v-toolbar-title>
    </v-toolbar>
    <v-content>
      <v-container fluid>
        <v-layout row mb-4>
          <v-flex>
            <graph></graph>
          </v-flex>
        </v-layout>
      </v-container>
    </v-content>
    <v-footer dark></v-footer>
    <popup-message></popup-message>
  </v-app>
</template>

<script>
import graph from "./components/graph.vue";
import popupMessage from './components/popup-message.vue';

export default {
  components: {
    graph,
    popupMessage
  }
};
</script>

16 行

<popup-message></popup-message>

新增 popup-message component。

popup-message.vue

<template>
  <v-snackbar v-model="snackbar" :timeout="timeout" :color="color" top>
    {{ notificationText }}
    <v-btn flat @click="snackbar = false">Close</v-btn>
  </v-snackbar>
</template>

<script>
let popupNotification = function(args) {
  this.notificationText = args.message;
  this.color = args.color || 'success';
  this.snackbar = true;
};

export default {
  name: "popup-message",
  data: () => ({
    snackbar: false,
    notificationText: '',
    timeout: 3000,
    color: 'success'
  }),
  sockets: {
    popupNotification
  }
}
</script>

23 行

sockets: {
  popupNotification
}

sockets property 內宣告 popupNotification()

第 9 行

let popupNotification = function(args) {
  this.notificationText = args.message;
  this.color = args.color || 'success';
  this.snackbar = true;
};

args 會接收 WebSocket 所傳來的。

Conclusion

  • Vue-socket.io 善用 Vue 特性,比原生 socket.io 更容易使用

Sample Code

完整範例可在我的 GitHub 上找到

Reference

Mark Barton, Display a broadcast message using a Vuetify snackbar component which is sent via Socket.io