Using Socket.IO or other WebSocket Libraries with Vue.

Level Up Your Vue Game: Socket.IO & Websockets – From Zero to Real-Time Hero πŸ¦Έβ€β™‚οΈ

Alright, Vue developers! Buckle up, because we’re about to dive headfirst into the exhilarating world of WebSockets and how to wield them effectively with Vue.js. Forget those clunky HTTP requests that feel like sending smoke signals across the internet. We’re talking real-time, baby! Think instant updates, live chats, collaborative editing, and maybe even a futuristic robot butler that delivers you coffee based on your mood (okay, maybe not yet).

This lecture is designed to take you from "WebSockets? Huh?" to "I’m building a real-time Vue app and it’s awesome!" We’ll cover the fundamentals, dive into Socket.IO (our friendly neighborhood WebSocket abstraction library), and explore practical examples to get you building.

Prerequisites:

  • Basic understanding of Vue.js (components, data binding, events)
  • Node.js and npm/yarn installed on your machine (we’re going to need a server, after all)
  • A healthy dose of curiosity and a willingness to occasionally scratch your head in confusion (it’s part of the learning process!)

Lecture Outline:

  1. Why Websockets? Ditch the Refresh Button! (The problem with HTTP polling and the magic of real-time communication)
  2. WebSockets 101: The Two-Way Street (Understanding the basics, the protocol, and the handshake)
  3. Introducing Socket.IO: Your WebSocket Superhero (Why use it? Key features and advantages)
  4. Setting Up Our Development Environment: The Real-Time Batcave (Installing Node.js, setting up a basic Express server, and adding Socket.IO)
  5. Building a Basic Chat Application: Hello, Real-Time World! (Server-side code, client-side Vue code, emitting and receiving messages)
  6. Advanced Socket.IO Techniques: Become a WebSocket Wizard πŸ§™β€β™‚οΈ (Namespaces, rooms, broadcasting, acknowledgements)
  7. Integrating Socket.IO with Vuex: State Management on Steroids (Keeping your real-time data in sync with your Vuex store)
  8. Error Handling and Debugging: Taming the WebSocket Wild West 🀠 (Dealing with disconnections, timeouts, and other potential issues)
  9. Security Considerations: Locking Down Your Real-Time Fortress πŸ›‘οΈ (Authentication, authorization, and preventing malicious attacks)
  10. Beyond the Basics: Where to Go From Here? (Exploring other WebSocket libraries, real-world use cases, and advanced topics)

1. Why Websockets? Ditch the Refresh Button! πŸ”„βž‘οΈπŸš€

Imagine you’re building a stock ticker application. With traditional HTTP requests, you’d have to constantly send requests to the server every few seconds to get the latest stock prices. This is called polling. It’s like constantly asking "Are we there yet?" on a road trip – inefficient and annoying!

Problems with HTTP Polling:

  • Overhead: Lots of unnecessary requests, even when the data hasn’t changed. Think of it like calling your friend every minute to ask if they’ve moved an inch.
  • Latency: Delay between the data changing on the server and being displayed on the client. You’re always a few seconds behind, which is terrible for real-time applications.
  • Server Load: All those requests can put a strain on your server, especially with a large number of users. Imagine everyone in the world calling your friend every minute… they’d probably block you.

Enter WebSockets!

WebSockets provide a full-duplex communication channel over a single TCP connection. Think of it as a permanent, two-way street between the client and the server. The server can push updates to the client as soon as they happen, without the client having to constantly ask. It’s like having a direct line to your friend who automatically tells you when they’ve moved.

Benefits of WebSockets:

  • Real-time Communication: Instant updates and low latency.
  • Efficiency: Less overhead and reduced server load.
  • Bidirectional Communication: Both the client and the server can send data at any time.
  • Stateful Connection: The connection remains open, allowing for persistent communication.

In short, WebSockets are the superheroes of real-time communication! They rescue us from the tyranny of the refresh button and empower us to build truly interactive and engaging applications.

2. WebSockets 101: The Two-Way Street ↔️

Let’s break down the core concepts behind WebSockets.

  • Protocol: WebSockets use the ws:// or wss:// (secure) protocols. They’re similar to http:// and https://, but designed for persistent connections.
  • Handshake: The process begins with an HTTP handshake. The client sends an HTTP request to the server asking to upgrade the connection to a WebSocket. If the server agrees, it sends back a 101 Switching Protocols response, and the WebSocket connection is established. It’s like asking to join a secret club with a secret handshake.
  • Full-Duplex Communication: Once the connection is established, both the client and the server can send data to each other simultaneously. Think of it as a phone call where both parties can talk at the same time.
  • Frames: Data is transmitted in frames, which are smaller units of data. This allows for efficient transmission and handling of large amounts of data.

Simplified Analogy:

Imagine you’re ordering pizza.

  • HTTP Polling: You call the pizza place every 5 minutes to ask if your pizza is ready.
  • WebSockets: You call the pizza place, and they keep the line open. They tell you exactly when the pizza is in the oven, when it’s being delivered, and when it’s at your door. Real-time pizza updates! πŸ•

Table: HTTP vs. WebSockets

Feature HTTP WebSockets
Communication Request-Response (Half-Duplex) Full-Duplex
Connection Stateless (Each request is independent) Stateful (Persistent connection)
Latency High Low
Overhead High Low
Use Cases General web browsing, static content Real-time applications, live updates

3. Introducing Socket.IO: Your WebSocket Superhero πŸ¦Έβ€β™‚οΈ

While you can work directly with the raw WebSocket API, it’s often a bit like trying to build a house with just a hammer and some nails. Socket.IO provides a higher-level abstraction that makes working with WebSockets much easier and more enjoyable.

Why Use Socket.IO?

  • Simplified API: Provides a simpler and more intuitive API for sending and receiving messages. Less boilerplate code!
  • Cross-Browser Compatibility: Handles browser differences and provides fallback mechanisms (like long polling) for older browsers that don’t fully support WebSockets. It’s like having a universal translator for your real-time communication.
  • Automatic Reconnection: Automatically tries to reconnect if the connection is lost. Ensures your application stays connected even in unstable network conditions.
  • Namespaces: Allows you to divide your application into logical channels, making it easier to manage different types of real-time data.
  • Rooms: Allows you to group clients together and send messages to specific groups. Imagine different chat rooms for different topics.
  • Broadcasting: Allows you to send messages to all connected clients. Think of it as a public announcement.
  • Acknowledgements: Allows you to ensure that messages have been successfully received by the other party. Like a read receipt for your real-time messages.

Key Features of Socket.IO:

  • Events: Communication is based on events. You emit events from the client and the server, and listen for those events on the other side.
  • Namespaces: Separate logical channels within your application.
  • Rooms: Group clients together for targeted communication.
  • Broadcasting: Send messages to all connected clients.
  • Acknowledgements: Verify message delivery.

In essence, Socket.IO is like a Swiss Army knife for WebSockets. It provides a rich set of features and abstractions that make building real-time applications a breeze.

4. Setting Up Our Development Environment: The Real-Time Batcave πŸ¦‡

Time to get our hands dirty! Let’s set up the necessary tools and create a basic project structure.

Steps:

  1. Create a Project Directory:

    mkdir vue-socket-io-demo
    cd vue-socket-io-demo
  2. Initialize a Node.js Project:

    npm init -y  # Or yarn init -y
  3. Install Dependencies:

    npm install express socket.io vue  # Or yarn add express socket.io vue
  4. Create Server File (server.js):

    // server.js
    const express = require('express');
    const http = require('http');
    const { Server } = require("socket.io");
    
    const app = express();
    const server = http.createServer(app);
    const io = new Server(server, {
        cors: {
            origin: "http://localhost:8080", // Replace with your Vue app's URL
            methods: ["GET", "POST"]
        }
    });
    
    io.on('connection', (socket) => {
        console.log('A user connected');
    
        socket.on('disconnect', () => {
            console.log('A user disconnected');
        });
    });
    
    const PORT = process.env.PORT || 3000;
    
    server.listen(PORT, () => {
        console.log(`Server listening on port ${PORT}`);
    });
  5. Create Vue Project (using Vue CLI):

    vue create client # Or choose your preferred Vue CLI options.  Name the folder "client"
    cd client
    npm install socket.io-client  # Or yarn add socket.io-client

Explanation:

  • express: A lightweight Node.js web framework for creating our server.
  • socket.io: The Socket.IO library for the server-side.
  • vue: The Vue.js framework for the client-side.
  • socket.io-client: The Socket.IO library for the client-side.
  • server.js: The main file for our Node.js server. It sets up an Express server, creates a Socket.IO instance, and listens for connections. The cors option is crucial for allowing your Vue app (running on a different port) to connect to the server. Remember to replace http://localhost:8080 with the actual URL of your Vue app!

Directory Structure:

vue-socket-io-demo/
β”œβ”€β”€ client/          # Vue.js client application
β”‚   β”œβ”€β”€ node_modules/
β”‚   β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ package.json
β”‚   └── ...
β”œβ”€β”€ node_modules/
β”œβ”€β”€ server.js
β”œβ”€β”€ package.json
└── ...

To run the server:

node server.js

To run the Vue application:

cd client
npm run serve # Or yarn serve

Now you should have a basic Node.js server running with Socket.IO enabled, and a separate Vue.js application. The server is listening for connections, and the Vue app is ready to connect to it.

5. Building a Basic Chat Application: Hello, Real-Time World! πŸ’¬

Let’s build a simple chat application to demonstrate the power of Socket.IO and Vue.js.

Server-Side Code (server.js – Updated):

// server.js
const express = require('express');
const http = require('http');
const { Server } = require("socket.io");

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: "http://localhost:8080", // Replace with your Vue app's URL
        methods: ["GET", "POST"]
    }
});

io.on('connection', (socket) => {
    console.log('A user connected');

    socket.on('chat message', (msg) => {
        console.log('message: ' + msg);
        io.emit('chat message', msg); // Broadcast the message to all connected clients
    });

    socket.on('disconnect', () => {
        console.log('A user disconnected');
    });
});

const PORT = process.env.PORT || 3000;

server.listen(PORT, () => {
    console.log(`Server listening on port ${PORT}`);
});

Client-Side Code (client/src/App.vue):

<template>
  <div id="app">
    <h1>Real-Time Chat</h1>
    <ul id="messages">
      <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
    </ul>
    <form @submit.prevent="sendMessage">
      <input v-model="newMessage" autocomplete="off" />
      <button>Send</button>
    </form>
  </div>
</template>

<script>
import { io } from "socket.io-client";

export default {
  data() {
    return {
      socket: null,
      newMessage: "",
      messages: [],
    };
  },
  mounted() {
    this.socket = io("http://localhost:3000"); // Replace with your server's URL

    this.socket.on("chat message", (msg) => {
      this.messages.push(msg);
    });
  },
  methods: {
    sendMessage() {
      this.socket.emit("chat message", this.newMessage);
      this.newMessage = "";
    },
  },
};
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

#messages {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

#messages li {
  padding: 5px 10px;
}

#messages li:nth-child(odd) {
  background: #f9f9f9;
}
</style>

Explanation:

  • Server-Side:
    • We listen for the chat message event from the client.
    • When we receive a message, we broadcast it to all connected clients using io.emit('chat message', msg). This ensures everyone sees the message.
  • Client-Side:
    • We import io from socket.io-client.
    • In the mounted lifecycle hook, we connect to the Socket.IO server. Make sure to replace http://localhost:3000 with your server’s URL!
    • We listen for the chat message event from the server.
    • When we receive a message, we add it to the messages array, which is displayed in the template.
    • The sendMessage method emits the chat message event to the server when the user submits the form.

Run both the server and the Vue application. Open the Vue app in multiple browser windows. Type a message in one window and see it appear in all the other windows in real-time! Congratulations, you’ve built your first real-time chat application! πŸŽ‰

6. Advanced Socket.IO Techniques: Become a WebSocket Wizard πŸ§™β€β™‚οΈ

Now that we have a basic chat application working, let’s explore some advanced Socket.IO features that can take your real-time applications to the next level.

a) Namespaces:

Namespaces allow you to divide your application into logical channels. Imagine you have different departments in your company, and each department needs its own communication channel.

Server-Side (server.js – Updated):

// server.js
// ... (previous code) ...

const newsNamespace = io.of('/news');

newsNamespace.on('connection', (socket) => {
    console.log('A user connected to the news namespace');

    socket.on('news update', (data) => {
        console.log('Received news update:', data);
        newsNamespace.emit('news update', data); // Broadcast to the news namespace
    });
});

Client-Side (client/src/components/NewsFeed.vue – Create a new component):

<template>
  <div>
    <h2>News Feed</h2>
    <ul>
      <li v-for="(item, index) in news" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
import { io } from "socket.io-client";

export default {
  data() {
    return {
      news: [],
      socket: null,
    };
  },
  mounted() {
    this.socket = io("http://localhost:3000/news"); // Connect to the news namespace

    this.socket.on("news update", (data) => {
      this.news.push(data);
    });
  },
};
</script>

Explanation:

  • Server-Side: We create a new namespace using io.of('/news'). Any clients connecting to http://localhost:3000/news will be part of this namespace.
  • Client-Side: We connect to the namespace by specifying the namespace URL in the io() function: io("http://localhost:3000/news").

b) Rooms:

Rooms allow you to group clients together and send messages to specific groups. Imagine different chat rooms for different topics.

Server-Side (server.js – Updated):

// server.js
// ... (previous code) ...

io.on('connection', (socket) => {
    // ... (previous code) ...

    socket.on('join room', (room) => {
        socket.join(room);
        console.log(`User joined room: ${room}`);
        socket.emit('message', `Welcome to room ${room}!`); // Send a welcome message only to the user
    });

    socket.on('room message', (data) => {
        io.to(data.room).emit('room message', data.message); // Send message to specific room
    });
});

Client-Side (client/src/components/ChatRoom.vue – Create a new component):

<template>
  <div>
    <h2>Chat Room</h2>
    <label for="room">Room:</label>
    <input type="text" id="room" v-model="roomName" />
    <button @click="joinRoom">Join Room</button>

    <ul id="messages">
      <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
    </ul>
    <form @submit.prevent="sendMessage">
      <input v-model="newMessage" autocomplete="off" />
      <button>Send</button>
    </form>
  </div>
</template>

<script>
import { io } from "socket.io-client";

export default {
  data() {
    return {
      socket: null,
      newMessage: "",
      messages: [],
      roomName: "",
    };
  },
  mounted() {
    this.socket = io("http://localhost:3000");

    this.socket.on("message", (msg) => {
      this.messages.push(msg);
    });

    this.socket.on("room message", (msg) => {
      this.messages.push(msg);
    });
  },
  methods: {
    joinRoom() {
      this.socket.emit("join room", this.roomName);
    },
    sendMessage() {
      this.socket.emit("room message", { room: this.roomName, message: this.newMessage });
      this.newMessage = "";
    },
  },
};
</script>

Explanation:

  • Server-Side:
    • We listen for the join room event. When a client joins a room, we use socket.join(room) to add them to the room.
    • We listen for the room message event. To send a message to a specific room, we use io.to(room).emit('room message', data.message). This ensures the message is only sent to clients in that room.
  • Client-Side:
    • We emit the join room event when the user clicks the "Join Room" button.
    • We emit the room message event with the room name and the message content when the user submits the form.

c) Broadcasting:

We’ve already used broadcasting in the basic chat example. io.emit() sends a message to all connected clients (except the sender). socket.broadcast.emit() sends a message to all connected clients except the sender.

d) Acknowledgements:

Acknowledgements allow you to verify that a message has been successfully received by the other party.

Server-Side (server.js – Updated):

// server.js
// ... (previous code) ...

io.on('connection', (socket) => {
    // ... (previous code) ...

    socket.on('message with ack', (data, callback) => {
        console.log('Received message:', data);
        callback({ status: 'received' }); // Send an acknowledgement back to the client
    });
});

Client-Side (client/src/components/AcknowledgementExample.vue – Create a new component):

<template>
  <div>
    <h2>Acknowledgement Example</h2>
    <button @click="sendMessage">Send Message with Acknowledgement</button>
    <p v-if="ackMessage">Acknowledgement: {{ ackMessage }}</p>
  </div>
</template>

<script>
import { io } from "socket.io-client";

export default {
  data() {
    return {
      socket: null,
      ackMessage: "",
    };
  },
  mounted() {
    this.socket = io("http://localhost:3000");
  },
  methods: {
    sendMessage() {
      this.socket.emit("message with ack", "Hello from the client!", (data) => {
        console.log("Acknowledgement received:", data);
        this.ackMessage = data.status;
      });
    },
  },
};
</script>

Explanation:

  • Server-Side: The socket.on() callback now includes a callback function. This function is used to send an acknowledgement back to the client.
  • Client-Side: The socket.emit() function now includes a third argument: a callback function that will be executed when the server sends an acknowledgement.

7. Integrating Socket.IO with Vuex: State Management on Steroids πŸ’ͺ

When building complex real-time applications, you’ll often want to manage your real-time data using Vuex, Vue’s official state management library.

Steps:

  1. Install Vuex (if you haven’t already):

    cd client
    npm install vuex  # Or yarn add vuex
  2. Create a Vuex Store (client/src/store/index.js):

    // client/src/store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    import { io } from "socket.io-client";
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        messages: [],
        socket: null,
      },
      mutations: {
        SET_SOCKET(state, socket) {
          state.socket = socket;
        },
        ADD_MESSAGE(state, message) {
          state.messages.push(message);
        },
      },
      actions: {
        connectSocket({ commit }) {
          const socket = io("http://localhost:3000"); // Replace with your server's URL
          commit('SET_SOCKET', socket);
    
          socket.on('chat message', (msg) => {
            commit('ADD_MESSAGE', msg);
          });
        },
        sendMessage({ state }, message) {
          state.socket.emit('chat message', message);
        },
      },
      getters: {
        getMessages: state => state.messages,
      },
    })
  3. Update App.vue to use Vuex:

    <template>
      <div id="app">
        <h1>Real-Time Chat (with Vuex)</h1>
        <ul id="messages">
          <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
        </ul>
        <form @submit.prevent="sendMessage">
          <input v-model="newMessage" autocomplete="off" />
          <button>Send</button>
        </form>
      </div>
    </template>
    
    <script>
    import { mapGetters, mapActions } from 'vuex';
    
    export default {
      data() {
        return {
          newMessage: "",
        };
      },
      computed: {
        ...mapGetters(['getMessages']),
        messages() {
          return this.getMessages;
        },
      },
      mounted() {
        this.connectSocket();
      },
      methods: {
        ...mapActions(['connectSocket', 'sendMessage']),
        submitMessage() {
          this.sendMessage(this.newMessage);
          this.newMessage = "";
        },
      },
    };
    </script>

Explanation:

  • Vuex Store:
    • We store the Socket.IO instance and the messages in the state.
    • We use mutations to update the state.
    • We use actions to handle asynchronous operations, such as connecting to the socket and sending messages. connectSocket establishes the connection and listens for chat message events. sendMessage emits the chat message event to the server.
    • We use getters to access the state.
  • App.vue:
    • We use mapGetters to map the getMessages getter to a computed property called messages.
    • We use mapActions to map the connectSocket and sendMessage actions to methods.
    • We call connectSocket in the mounted lifecycle hook.

Now, your chat application is using Vuex to manage the state of the messages. This makes it easier to manage and share real-time data across your application.

8. Error Handling and Debugging: Taming the WebSocket Wild West 🀠

WebSockets can be temperamental. Connections can drop, messages can get lost, and things can generally go wrong. Here’s how to handle those situations:

  • Disconnections: Handle the disconnect event on both the client and the server. This allows you to gracefully handle disconnections and attempt to reconnect.

    // Server-Side
    io.on('connection', (socket) => {
        socket.on('disconnect', () => {
            console.log('User disconnected');
        });
    });
    
    //Client-Side
    this.socket.on('disconnect', () => {
        console.log('Disconnected from server');
    });
  • Reconnection Attempts: Socket.IO automatically attempts to reconnect if the connection is lost. You can configure the reconnection attempts and timeouts.

    //Client-Side - During socket initialization
    this.socket = io("http://localhost:3000", {
      reconnectionAttempts: 5, // Maximum number of reconnection attempts
      reconnectionDelay: 2000 // Delay between reconnection attempts (in milliseconds)
    });
  • Timeouts: Set timeouts for sending and receiving messages to prevent your application from hanging indefinitely.

  • Logging: Use console.log statements to log events and data. This can help you track down errors and understand what’s happening in your application.

  • Browser Developer Tools: Use your browser’s developer tools to inspect WebSocket connections and messages. This can be invaluable for debugging real-time applications. In Chrome, look at the "Network" tab and filter by "WS" to see WebSocket connections.

  • Server-Side Monitoring: Implement server-side monitoring to track the number of connected clients, message rates, and other important metrics. This can help you identify performance issues and potential problems.

9. Security Considerations: Locking Down Your Real-Time Fortress πŸ›‘οΈ

Security is paramount when building real-time applications. Here are some important security considerations:

  • Authentication: Verify the identity of users before allowing them to connect to your WebSocket server. You can use standard authentication mechanisms like JWT (JSON Web Tokens) or cookies. Send the token or cookie during the initial WebSocket handshake.

    // Server-Side (Example using JWT)
    io.use((socket, next) => {
        if (socket.handshake.auth && socket.handshake.auth.token) {
            jwt.verify(socket.handshake.auth.token, 'your_secret_key', (err, decoded) => {
                if (err) return next(new Error('Authentication error'));
                socket.decoded = decoded;
                next();
            });
        } else {
            next(new Error('Authentication error'));
        }
    });
  • Authorization: Control which users have access to which resources and functionalities. You can use roles and permissions to manage access control.

  • Input Validation: Always validate user input to prevent malicious attacks, such as cross-site scripting (XSS) and SQL injection.

  • Rate Limiting: Limit the number of requests that a user can make within a certain time period to prevent denial-of-service (DoS) attacks.

  • Secure WebSocket (wss://): Always use the wss:// protocol to encrypt communication between the client and the server. This prevents eavesdropping and man-in-the-middle attacks. You’ll need to obtain an SSL certificate for your server.

  • Origin Validation: Configure CORS (Cross-Origin Resource Sharing) to only allow connections from trusted origins. This prevents unauthorized websites from connecting to your WebSocket server.

  • Regular Security Audits: Conduct regular security audits to identify and address potential vulnerabilities.

10. Beyond the Basics: Where to Go From Here? ➑️

Congratulations! You’ve made it to the end of this epic WebSocket journey! You’re now equipped with the knowledge and skills to build amazing real-time applications with Vue.js and Socket.IO.

Here are some ideas for further exploration:

  • Explore other WebSocket libraries: While Socket.IO is great, there are other libraries like ws and SockJS that you might want to investigate.
  • Build more complex real-time applications: Try building a collaborative text editor, a real-time game, or a live dashboard.
  • Learn about advanced WebSocket topics: Explore topics like WebSocket proxies, load balancing, and scaling.
  • Contribute to open-source WebSocket projects: Help improve the WebSocket ecosystem by contributing to open-source libraries and tools.
  • Stay up-to-date with the latest WebSocket standards and best practices: The WebSocket world is constantly evolving, so it’s important to stay informed about the latest developments.

Real-World Use Cases:

  • Online Gaming: Real-time multiplayer games rely heavily on WebSockets for instant communication between players.
  • Financial Applications: Stock tickers, trading platforms, and other financial applications need real-time data updates.
  • Collaborative Editing: Google Docs, Figma, and other collaborative editing tools use WebSockets to allow multiple users to work on the same document simultaneously.
  • Live Chat: Customer support chat applications and other live chat systems use WebSockets for real-time communication between agents and customers.
  • IoT (Internet of Things): IoT devices use WebSockets to communicate with servers and other devices in real-time.
  • Streaming Services: Live video and audio streaming services often use WebSockets for real-time data transfer.

Remember, the possibilities are endless! Go forth and build amazing real-time applications! πŸŽ‰

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *