Websocket topic return empty object


#1

Hey, i only use 1 channel - it’s called “user” and there are no any topics.

In code bellow you can see how i am currently using websocket. Sometimes at subscription variable i am getting empty object (null). But client is still connected and i am don’t understand why that happens.

WebSocket.updateAccountStatus = async ({ id, account, status, data }) => {
	console.log('Event - WebSocket - updateAccountStatus', account, status);

	const socketId = await SocketService.Get(id);
	if (socketId === -1) return;

	const channel = Ws.getChannel('user');
	if (!channel) {
		console.error('no_channel');
		return;
	}

	const subscription = channel.topic('user');
	if (!subscription) {
		console.error('no_subscription', { channel, subscription });
		return;
	}

	data.account = account;
	data.status = status;

	await subscription.emitTo(
		'message',
		{
			type: 'status',
			data
		},
		[socketId]
	);
	console.log('Event - WebSocket - updateAccountStatus send');
};

My Log

SocketService Add: 3
SocketService Update DB Socket
connected socket user#cjn4pjnm8000034w0415kxa8k 3
Event - Server.updateAccountStatus armvac { status: 'playing', playing_games: [ 730 ] }
Event - WebSocket - updateAccountStatus armvac playing
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armvac { status: 'online' }
Event - WebSocket - updateAccountStatus armvac online
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armvac { status: 'offline', playing_games: [] }
Event - WebSocket - updateAccountStatus armvac offline
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armvac { status: 'steamGuard' }
Event - WebSocket - updateAccountStatus armvac steamGuard
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armvac { status: 'error',
  error: { id: 65, message: 'InvalidLoginAuthCode' },
  playing_games: [] }
Event - Server.updateAccountStatus armvac { status: 'offline', playing_games: [] }
Event - WebSocket - updateAccountStatus armvac error
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
Event - WebSocket - updateAccountStatus send
Event - WebSocket - updateAccountStatus armvac offline
SocketService Get: { userId: 3 }
SocketService Get DB - Searching
SocketService Get DB - Found user#cjn4pjnm8000034w0415kxa8k
no_subscription { channel:
   Channel {
     name: 'user',
     _onConnect: 'UserController',
     _channelControllerListeners: [],
     subscriptions: Map { 'user' => Set {} },
     _middleware: [ 'auth' ],
     deleteSubscription: [Function: bound ] },
  subscription: null }
Event - Server.updateAccountStatus armvac { status: 'steamGuard' }
Event - WebSocket - updateAccountStatus armvac steamGuard
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn4pjnm8000034w0415kxa8k
no_subscription { channel:
   Channel {
     name: 'user',
     _onConnect: 'UserController',
     _channelControllerListeners: [],
     subscriptions: Map { 'user' => Set {} },
     _middleware: [ 'auth' ],
     deleteSubscription: [Function: bound ] },
  subscription: null }

#2

Any ideas where i can dig?


#3

It’s impossible to know what’s the reason behind getting an empty object. Also do you make sure that client is still connected (what’s your way of verifying it)?

Finally, I suggest sharing a repo, with the code to reproduce the issue


#4

Hey, @virk
Yes i am sure, client must be online. From server log you can see that “disconnected socket” is never appers. And from client log “close”, “error” events never happens. It would be hard to share my code. But if info that i just share with you won’t help to find my issue. I will try to make a repo and share my code.

Websocket user controller

'use strict';

const SocketService = use('App/Services/SocketService');

class UserController {
	constructor({ socket, request, auth }) {
		this.socket = socket;
		this.request = request;
		this.auth = auth;

		this.PushSocket().then(() =>
			console.log(
				'connected socket',
				socket.id,
				auth.user.id,
				socket.topic
			)
		);
	}

	async PushSocket() {
		await SocketService.Add(this.auth.user.id, this.socket.id);
	}

	async DeleteSocket() {
		await SocketService.Delete(this.auth.user.id);
	}

	onMessage(message) {
		console.log('receive message from socket', this.socket.id, message);
	}

	onClose() {
		this.DeleteSocket().then(() =>
			console.log('disconnected socket', this.socket.id)
		);
	}

	onError(error) {
		console.log('socket error', error);
	}
}

module.exports = UserController;

SocketService - i use it to globaly store socket.id

const Socket = use('App/Models/Socket');

let socketList = {};
class SocketService {
	async Add(userId, socketId) {
		socketList[userId] = socketId;
		console.log('SocketService Add:', userId);
		const userSocket = await Socket.findBy('user_id', userId);
		if (userSocket) {
			console.log('SocketService Update DB Socket');
			userSocket.socketId = socketId;
			userSocket.online = true;
			await userSocket.save();
		} else {
			console.log('SocketService Create DB Socket');
			const newSocket = new Socket();
			newSocket.fill({ user_id: userId, socketId });
			await newSocket.save();
		}
	}

	async Delete(userId) {
		console.log('SocketService Delete:', userId);
		if (!socketList[userId]) return;
		delete socketList[userId];
		console.log('SocketService Deleted');
		const userSocket = await Socket.findBy('user_id', userId);
		if (!userSocket) return;

		userSocket.online = false;
		await userSocket.save();
	}

	async Get(userId) {
		console.log('SocketService Get:', { userId });
		if (!socketList[userId]) {
			console.log('SocketService Get DB - Searching');
			const userSocket = await Socket.findBy('user_id', userId);
			if (!userSocket || !userSocket.online) return -1;

			console.log('SocketService Get DB - Found', userSocket.socketId);

			socketList[userId] = userSocket.socketId;
			return userSocket.socketId;
		}

		console.log('SocketService Found:', userId, socketList[userId]);
		return socketList[userId];
	}

	ToArray() {
		return Object.values(socketList);
	}
}

module.exports = new SocketService();

This is Websocket class that i use at client side

import babelPolyfill from 'babel-polyfill';
import Ws from '@adonisjs/websocket-client';

class WebSocket {
	constructor() {
		this.ws = Ws('ws://localhost:3333', { reconnectionAttempts: 200 });
		this.IsConnected = false;
	}

	SetStore(store) {
		this.store = store;
	}

	Connect(token) {
		if (this.IsConnected) return;
		console.log('Websocket - Connect');
		this.ws.withJwtToken(token).connect();
		this.ws.on('open', () => this.BindEvents());
		this.ws.on('error', error => console.log('WebSocket Error', error));
		this.ws.on('close', () => {
			console.log('Websocket - Close');
			this.IsConnected = false;
			this.store.commit('setConnected', this.IsConnected);
		});
	}

	BindEvents() {
		const userChannel =
			this.ws.getSubscription('user') || this.ws.subscribe('user');
		userChannel.on('ready', () => {
			console.log('Websocket - Ready');
			this.IsConnected = true;
			this.store.commit('setConnected', this.IsConnected);
			//userChannel.emit('message', 'hello');
		});

		userChannel.on('message', message => {
			console.log(
				'Websocket - Subscription',
				this.ws.getSubscription('user')
			);
			console.log('Websocket - Receive', message);

			const { type, data } = message;
			const { status } = data;

			if (type === 'status') {
				this.store.dispatch('account/handleStatus', data);
				switch (status) {
					case 'offline':
					case 'timeout':
					case 'steamGuard':
						this.store.dispatch('idle/validateSteamGuard', data);
						break;

					case 'tempBan':
					case 'unban':
					case 'ban':
						this.store.dispatch('account/handleBan', data);
						break;
				}
			}
		});
		userChannel.on('close', () => {
			console.log('Websocket - userChannel close');
		});
	}

	Disconnect() {
		if (!this.IsConnected) return;
		console.log('Websocket - Disconnect');
		this.ws.close();
	}
}

const ws = new WebSocket();
export default ws;

export function createWebSocketPlugin() {
	return store => ws.SetStore(store);
}

Last client log

[HMR] Waiting for update signal from WDS... log.js:24
adonis:websocket connection options 
Object { path: "adonis-ws", reconnection: true, reconnectionAttempts: 200, reconnectionDelay: 1000, query: null, encoder: {…} }
 +0ms Ws.browser.js:1139
Websocket - Connect socket.js:16
adonis:websocket creating socket connection on ws://localhost:3333/adonis-ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjMsImlhdCI6MTUzOTM1NzYyMX0.uyZULK9KLdfquTqfCgLh0tz0eFDBxO60C5BED_bFVMU url +39s Ws.browser.js:1139
adonis:websocket opened +22ms Ws.browser.js:1139
adonis:websocket open packet +2ms Ws.browser.js:1139
adonis:websocket processing pre connection subscriptions 
Array []
 +1ms Ws.browser.js:1139
adonis:websocket initiating subscription for user topic with server +0ms Ws.browser.js:1139
adonis:websocket join ack packet +51ms Ws.browser.js:1139
adonis:websocket clearing emit buffer for user topic after subscription ack +0ms Ws.browser.js:1139
Websocket - Ready socket.js:31
adonis:websocket event packet +17s Ws.browser.js:1139
Websocket - Subscription 
Object { topic: "user", connection: {…}, emitter: {}, _state: "open", _emitBuffer: [] }
socket.js:38
Websocket - Receive 
{…}
​
data: Object { account: "armboost", status: "steamGuard" }
​
type: "status"
​
<prototype>: Object { … }
socket.js:42
adonis:websocket event packet +3s Ws.browser.js:1139
Websocket - Subscription 
Object { topic: "user", connection: {…}, emitter: {}, _state: "open", _emitBuffer: [] }
socket.js:38
Websocket - Receive 
{…}
​
data: Object { error: {…}, account: "armboost", status: "error" }
​
type: "status"
​
<prototype>: Object { … }
socket.js:42
adonis:websocket pong packet +5s Ws.browser.js:1139
adonis:websocket pong packet +25s
Ws.browser.js:1139
adonis:websocket pong packet +26s Ws.browser.js:1139
adonis:websocket pong packet +25s

Last server log

SocketService Add: 3
SocketService Update DB Socket
connected socket user#cjn65s6uq0000pww0bmmhodq0 3 user
Event - Server.updateAccountStatus armboost { status: 'steamGuard' }
Event - WebSocket - updateAccountStatus armboost steamGuard
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn65s6uq0000pww0bmmhodq0
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armboost { status: 'error',
  error: { id: 65, message: 'InvalidLoginAuthCode' },
  playing_games: [] }
Event - Server.updateAccountStatus armboost { status: 'offline', playing_games: [] }
Event - WebSocket - updateAccountStatus armboost error
SocketService Get: { userId: 3 }
SocketService Found: 3 user#cjn65s6uq0000pww0bmmhodq0
Event - WebSocket - updateAccountStatus send
Event - WebSocket - updateAccountStatus armboost offline
SocketService Get: { userId: 3 }
SocketService Get DB - Searching
SocketService Get DB - Found user#cjn65s6uq0000pww0bmmhodq0
no_subscription { channel:
   Channel {
     name: 'user',
     _onConnect: 'UserController',
     _channelControllerListeners: [],
     subscriptions: Map { 'user' => Set {} },
     _middleware: [ 'auth' ],
     deleteSubscription: [Function: bound ] },
  subscription: null }

#5

Any ideas how can i understand where is issue?

Tryed to debug:

@adonis/websocket/src/Channel/index.js

At some point it can’t find topic. User is still connected i am 400% sure as this.deleteSubscription on server never happens. Info from client give me same answer - it’s still connected. There is only 1 place in code where i found topic.delete(subscription) it happens in this.deleteSubscription function, but it never happens

getTopicSubscriptions(topic) {
    if (!this.subscriptions.has(topic)) { // it's can't find topic
        this.subscriptions.set(topic, new Set());
    }
    return this.subscriptions.get(topic);
}

Log from it you can see that disconnect never happens

adding channel subscription for user topic
new subscription for topic { topic: 'user' }
SocketService Add: 3
SocketService Update DB Socket
connected socket user#cjnbtfk710000pcw00ckpvkkf 3 user
closing underlying connection
deleteSubscription user
removing channel subscription for user topic
removed subscription for user topic
SocketService Delete: 3
SocketService Deleted
disconnected socket user#cjnbtfk710000pcw00ckpvkkf
adding channel subscription for user topic
new subscription for topic { topic: 'user' }
SocketService Add: 3
SocketService Update DB Socket
connected socket user#cjnbtfwu60001pcw0230h4pbe 3 user
Event - Server.updateAccountStatus armboost { status: 'steamGuard' }
getFirstSubscription { subscriptions: Set { Socket { channel: [Channel], emitter: Emittery {} } } }
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armboost { status: 'error',  error: { id: 65, message: 'InvalidLoginAuthCode' },  playing_games: [] }
Event - Server.updateAccountStatus armboost { status: 'offline', playing_games: [] }
getFirstSubscription { subscriptions: Set { Socket { channel: [Channel], emitter: Emittery {} } } }
Event - WebSocket - updateAccountStatus send
getFirstSubscription { subscriptions: Set { Socket { channel: [Channel], emitter: Emittery {} } } }
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armboost { status: 'steamGuard' }
getFirstSubscription { subscriptions: Set { Socket { channel: [Channel], emitter: Emittery {} } } }
Event - WebSocket - updateAccountStatus send
Event - Server.updateAccountStatus armboost { status: 'error',  error: { id: 65, message: 'InvalidLoginAuthCode' },  playing_games: [] }
Event - Server.updateAccountStatus armboost { status: 'offline', playing_games: [] }
getFirstSubscription { subscriptions: Set { Socket { channel: [Channel], emitter: Emittery {} } } }
Event - WebSocket - updateAccountStatus send
getTopicSubscriptions - no topic user
getFirstSubscription { subscriptions: Set {} }
no_subscription
Event - Server.updateAccountStatus armboost { status: 'steamGuard' }
getFirstSubscription { subscriptions: Set {} }
no_subscription

@virk it’s hard to upload my full project to githab. Maybe you can connect to my pc with AnyDesk and look at this issue by your self?


#6

Can some one help me?
For some random reason getFirstSubscription return zero object for me. So i can’t send a WebSocket message from server to client. Client is not disconnected and still online.
getFirstSubscription is a function is inside @adonisjs/websocket package

At this video i am showing my issue.


#7

@virk i found the reason of my problem

When i delete this code from my start/server.js file all start work like it should. There are is a definetly a bug at @adonisjs/websocket lib with working on nodejs clusters