Cómo hacer namespaces dinámicos en Socket.io


Socket.io es una librería para hacer comunicaciones en tiempo real con Node.js utilizando Websockets y en este blog he dedicado varios artículos al respecto.

Sin embargo, una necesidad muy común al usar todas las funcionalidades de esta librería es usar correctamente los Namespace y los Rooms, el cual nacen para tener extensiones y una mejor organización de nuestros canales, pero este no es el post para explicar cada uno (http://socket.io/docs/rooms-and-namespaces/) si no para crear namespaces dinámicos y que por su implementación normal no es posible.

A continuación, un ejemplo donde se puede crear namespaces y unirse a ellos dinámicamente.

Servidor

  • Declaración de variables
	var MAX_CLIENTS = 5;
	var namespace_queue = [];
  • Funciones locales

    // Retorna un elemento que se desea buscar en un array
    function searchObjectOnArray(nameKey, myArray) {
        for (var i = 0; i < myArray.length; i++) {
            if (myArray[i].id === nameKey) {
                return myArray[i];
            }
        }
    }

    // Crea el namespace en la aplicación
    function createNamespace(data){
	var ns = {
                id : data.name,
                clients: 0, 
	};

	namespace_queue.push(ns);

	return ns;
    }
  • Ahora, es importante definir todos los eventos que evocaremos en nuestra aplicación de Frontend dentro de la siguiente sentencia.
io.of('').on('connection', function(socket){
    // Our Socket.io connection
});
  • Definir la creación del Namespace dinámico.
		socket.on('createNamespace',function(data,join_cb){
            
			createNamespace(data);

			join_cb({message:'Namespace created'});
		});
  • Por último la manera en cómo se une dinámicamente al namespace
        socket.on('JoinToApp', function (data, callback) {
            var namespaceToConnect = searchObjectOnArray(data.namespace, namespace_queue)
            if(namespaceToConnect.clients <= MAX_CLIENTS){
                var dynamicNamespace = io.of('/' + namespaceToConnect.id);
				
                dynamicNamespace.on('connection', function(ns_socket){
                        console.log('user connected to ' + namespaceToConnect.id);
                        dynamicNamespace.emit('hi', 'everyone!');
                    });
                    
			    namespaceToConnect.clients++;  
            }          
            
            callback({namespaces:namespace_queue});
        })

Código completo del servidor:


	var MAX_CLIENTS = 5;
	var namespace_queue = [];

    function searchObjectOnArray(nameKey, myArray) {
        for (var i = 0; i < myArray.length; i++) {
            if (myArray[i].id === nameKey) {
                return myArray[i];
            }
        }
    }

	function createNamespace(data){
		var ns = {
					//id: require('node-uuid')(),
                    id : data.name,
					clients: 0, 
				};

		namespace_queue.push(ns);

		return ns;
	}

	createNamespace({name: 'primer'});

	io.of('').on('connection', function(socket){     

		console.log('-' + socket.id);
	
		/// Welcome to the new client
		socket.emit('Welcome', {SocketId : socket.id});
        
        socket.on('JoinToApp', function (data, callback) {
            var namespaceToConnect = searchObjectOnArray(data.namespace, namespace_queue)
            if(namespaceToConnect.clients <= MAX_CLIENTS){
                var dynamicNamespace = io.of('/' + namespaceToConnect.id);
				
                dynamicNamespace.on('connection', function(ns_socket){
                        console.log('user connected to ' + namespaceToConnect.id);
                        dynamicNamespace.emit('hi', 'everyone!');
                    });
                    
			    namespaceToConnect.clients++;  
            }          
            
            callback({namespaces:namespace_queue});
        })
        
		socket.on('createNamespace',function(data,join_cb){
            
			createNamespace(data);

			join_cb({message:'Namespace created'});
		});	
	});

Cliente

Para probar este código fuente hice un cliente muy simple, pero que demuestra la creación de los namespace:

<input id="namespaceInput" type="text" placeholder="New namespace name">
<input id="namespaceToConnect" type="text" placeholder="namespace to connect">

<button onclick="javascript: createNamespace()">Create Namespace</button>
<button onclick="javascript: joinToNamespace()">Connect to Namespace</button>

<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script>
    var socket = null;
   (function(){
       socket = io.connect('http://localhost:3000/');        
   })()
   
   function createNamespace(){
       var namespaceName = document.getElementById("namespaceInput").value;
       socket.emit('createNamespace', {name : namespaceName}, function(data){
           alert(data.message);
       })
   }
   
   function joinToNamespace(){
       var name = document.getElementById("namespaceToConnect").value;
       socket.emit('JoinToApp', {namespace: name}, function(data){
            console.log('Namespaces created:');
            console.log(data)
            
            var ns_socket = io.connect('http://localhost:3000/' + name);
            ns_socket.on('connect',function(){
                console.log('joined namespace ' + name);
            });
            
            ns_socket.on('hi', function(data){
                console.log('hi ' + data)
            })
        });
       
   }
</script>

* Este código está creado con la versión 1.4.5 de Socket.io