NuxtJS+Express+SocketIO+JWT环境下,服务器完全加载后Emit事件停止推送的问题排查
Let's break down and fix the two main issues you're facing: the hello! undefined log message, and the loss of event reception after Webpack compilation.
1. Fixing hello! undefined
The undefined value here means your decoded JWT token doesn't have a name field in its payload. Let's debug and resolve this:
Step 1: Inspect the decoded token
First, modify your server-side authenticated handler to log the full decoded token structure so you can see what fields are actually present:
.on('authenticated', (socket) => { // Log the entire decoded token to check its structure console.log('Decoded JWT Token:', socket.decoded_token); // Use a fallback field if `name` doesn't exist const userName = socket.decoded_token.name || socket.decoded_token.username || socket.decoded_token.sub || 'Guest'; console.log(`hello! ${userName}`); })
Step 2: Verify JWT payload
Double-check how you're generating JWT tokens in your auth flow. Ensure the payload includes the field you're trying to access (e.g., name, username, or sub).
2. Fixing Event Reception Loss After Webpack Compilation
This happens because Nuxt's Hot Module Replacement (HMR) in development mode re-renders components, and your current socket setup creates a local connection that gets discarded when the component re-mounts. Here are two solid solutions:
Option A: Manage Socket via Vuex (Recommended)
Centralize your socket instance in Vuex to maintain a single connection across component re-renders, and clean up listeners when components unmount.
1. Add a Socket Vuex Module (store/modules/socket.js)
import io from 'socket.io-client'; export const state = () => ({ instance: null, isAuthenticated: false }); export const mutations = { SET_SOCKET(state, socket) { state.instance = socket; }, SET_AUTH_STATUS(state, status) { state.isAuthenticated = status; } }; export const actions = { initSocket({ commit, rootState }) { const token = rootState.user.token; const socket = io.connect('http://192.168.41.15:3000'); socket.on('connect', () => { socket.emit('authenticate', { token }) .on('authenticated', () => { commit('SET_AUTH_STATUS', true); console.log('Socket authenticated successfully'); }) .on('unauthorized', (msg) => { console.error(`Socket unauthorized: ${JSON.stringify(msg.data)}`); commit('SET_AUTH_STATUS', false); }); }); commit('SET_SOCKET', socket); return socket; }, disconnectSocket({ state, commit }) { if (state.instance) { state.instance.disconnect(); commit('SET_SOCKET', null); commit('SET_AUTH_STATUS', false); } } };
2. Update Your test.vue Component
<template> <div> <h1>Testing Socket.IO</h1> <v-text-field v-model="latestTickId" readonly /> <v-btn @click="sendTestEvent">Send Test</v-btn> </div> </template> <script> export default { data() { return { latestTickId: 0, tickListener: null, testListener: null }; }, async mounted() { // Initialize socket if it doesn't exist if (!this.$store.state.socket.instance) { await this.$store.dispatch('socket/initSocket'); } const socket = this.$store.state.socket.instance; // Save listener references to clean up later this.tickListener = (tickId) => { this.latestTickId = tickId; }; this.testListener = (data) => { console.log('Incoming test data:', data); }; // Attach listeners socket.on('tick', this.tickListener); socket.on('test', this.testListener); }, beforeDestroy() { const socket = this.$store.state.socket.instance; if (socket) { // Remove listeners to avoid memory leaks socket.off('tick', this.tickListener); socket.off('test', this.testListener); // Disconnect in dev mode to avoid stale connections during HMR if (process.env.NODE_ENV === 'development') { this.$store.dispatch('socket/disconnectSocket'); } } }, methods: { sendTestEvent() { const socket = this.$store.state.socket.instance; if (socket && this.$store.state.socket.isAuthenticated) { socket.emit('test', { id: 'abc123' }); } else { console.error('Socket not initialized or authenticated'); } } } }; </script>
Option B: Use a Nuxt Plugin for Global Socket
Create a client-side plugin to maintain a single socket instance across your app, with HMR support:
1. Create plugins/socket.js
import io from 'socket.io-client'; export default ({ app, store }, inject) => { const createSocket = () => { const token = store.state.user.token; const socket = io.connect('http://192.168.41.15:3000'); socket.on('connect', () => { socket.emit('authenticate', { token }) .on('authenticated', () => console.log('Socket authenticated')) .on('unauthorized', (msg) => console.error(`Socket unauthorized: ${JSON.stringify(msg.data)}`)); }); inject('socket', socket); return socket; }; // Initialize socket on app load let socket = createSocket(); // Handle HMR in dev mode if (process.env.NODE_ENV === 'development' && module.hot) { module.hot.accept(() => { socket.disconnect(); socket = createSocket(); }); } };
2. Register the Plugin in nuxt.config.js
export default { // ... other config plugins: [ { src: '~/plugins/socket.js', mode: 'client' } // Only run on client side ] };
3. Simplify test.vue
<template> <div> <h1>Testing Socket.IO</h1> <v-text-field v-model="latestTickId" readonly /> <v-btn @click="sendTestEvent">Send Test</v-btn> </div> </template> <script> export default { data() { return { latestTickId: 0, tickListener: null, testListener: null }; }, mounted() { this.tickListener = (tickId) => this.latestTickId = tickId; this.testListener = (data) => console.log('Incoming test data:', data); this.$socket.on('tick', this.tickListener); this.$socket.on('test', this.testListener); }, beforeDestroy() { this.$socket.off('tick', this.tickListener); this.$socket.off('test', this.testListener); }, methods: { sendTestEvent() { this.$socket.emit('test', { id: 'abc123' }); } } }; </script>
3. Fix Server-Side Socket Event Handling
Your current test event listener is attached to the root io instance instead of authenticated sockets. Move it inside the authenticated handler to ensure only authorized sockets can trigger it:
io.on( 'connection', socketioJwt.authorize({ secret: process.env.PRIVATE, timeout: 15000 }) ) .on('authenticated', (socket) => { console.log('Decoded token:', socket.decoded_token); console.log(`hello! ${socket.decoded_token.name || 'Guest'}`); // Attach test event listener to authenticated socket only socket.on('test', (data) => { console.log('Received test data:', data); io.emit('test', data); // Broadcast to all clients // Or use socket.emit('test', data) to reply only to the sender }); });
These changes should resolve both the undefined username issue and the event loss after Webpack compilation.
内容的提问来源于stack exchange,提问作者Selage

