From 3a30afe760bbee022fe179f6355e34d30ccb3b8a Mon Sep 17 00:00:00 2001
From: mcmlxxix <>
Date: Mon, 30 Nov 2009 21:37:04 +0000
Subject: [PATCH] Inter-BBS/Inter-node communication service (supersedes
 qengine.js)

---
 exec/commservice.js | 314 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 314 insertions(+)
 create mode 100644 exec/commservice.js

diff --git a/exec/commservice.js b/exec/commservice.js
new file mode 100644
index 0000000000..1b0145d270
--- /dev/null
+++ b/exec/commservice.js
@@ -0,0 +1,314 @@
+//load("commsync.js");
+/*
+	Inter-BBS/Inter-Node socket service
+	for Synchronet v3.15+ 
+	by Matt Johnson - 2009
+	Allows real-time gaming/chatting between local bbs nodes and remote bbs systems
+
+	The following files can be located in the Synchronet CVS repository:
+	
+	Add to "/sbbs/exec/" directory:
+		commservice.js
+	
+	Add to "/sbbs/exec/load/" directory:
+		commclient.js
+		chat.js
+		funclib.js
+		commsync.js
+	
+	Add to "/sbbs/ctrl" directory:
+		commservice.ini
+		commsync.ini
+	
+	Add to "/sbbs/ctrl/services.ini" file:
+
+	[Commserv]
+	Port=10088
+	MaxClients=20
+	Options=STATIC
+	Command=commservice.js MDJ.ATH.CX 10088
+	
+*/
+
+const normal_scope=	"#";
+const global_scope=	"!";
+const node_prefix=		"*";
+const local_prefix=	"&";
+
+const hub_address=argv[0];
+const hub_port=argv[1];
+const connection_timeout=2;
+const connection_attempts=5;
+
+var local_socks=[];
+var remote_socks=[];
+var data_queue=[];
+var remote_hub=false;
+
+//main service loop
+while(!server.terminated) 
+{
+	sock_cycle();
+	if(server.socket.poll()<1)
+	{
+		mswait(100);
+		continue;
+	}
+	store_socket(server.socket.accept());
+}
+
+function sock_cycle()
+{
+	if(count_local_sockets()>0 || count_remote_sockets()>0)
+	{
+		if(hub_address && hub_port)
+		{
+			if(!remote_hub || !remote_hub.is_connected)
+				hub_connect();
+		}
+		else if(hub_address || hub_port)
+		{
+			log("invalid main hub information");
+		}
+		inbound();
+		outbound();
+	}
+	else
+	{
+		if(remote_hub && remote_hub.is_connected)
+		{
+			hub_disconnect();
+		}
+	}
+}
+function inbound()
+{
+	//read data from each socket
+	for(var l in local_socks)
+	{
+		for(var s=0;s<local_socks[l].length;s++)
+		{
+			var socket=local_socks[l][s].sock;
+			if(socket.is_connected)
+			{
+				if(socket.data_waiting)
+				{
+					socket_receive(socket);
+				}
+			}
+			else
+			{
+				s=delete_local_session(l,s);
+			}
+		}
+	}
+	for(var r=0;r<remote_socks.length;r++)
+	{
+		var node=remote_socks[r];
+		if(node.sock.is_connected)
+		{
+			socket_receive(node.sock);
+		}
+		else
+		{
+			r=delete_remote_session(r);
+		}
+	}
+	if(remote_hub && remote_hub.is_connected)
+	{
+		socket_receive(remote_hub);
+	}
+}
+function outbound()
+{
+	//send all data to hub and to appropriate local socket group
+	for(var d=0;d<data_queue.length;d++)
+	{
+		var data=data_queue[d];
+		//if the central hub is connected and was not the origin of the data, send thru
+		if(remote_hub && remote_hub.is_connected)
+		{
+			socket_send_remote(data,remote_hub);
+		}
+		//send data to all remotely connected nodes
+		for(var r=0;r<remote_socks.length;r++)
+		{
+			var socket=remote_socks[r].sock;
+			if(socket.is_connected)
+			{
+				socket_send_remote(data,socket);
+			}
+			else
+			{
+				r=delete_remote_session(r);
+			}
+		}
+		switch(data.scope)
+		{
+			//if data is meant to be sent to all connected clients, do so (usually chat messages)
+			case global_scope:
+				for(var l in local_socks)
+				{
+					for(var s=0;s<local_socks[l].length;s++)
+					{
+						var socket=local_socks[l][s].sock;
+						if(socket.is_connected)
+						{
+							socket_send_local(data,socket);
+						}
+						else
+						{
+							s=delete_local_session(data.session,s);
+						}
+					}
+				}
+				break;
+			//distribute data to appropriate local sessions
+			case normal_scope:
+			default:
+				for(var s=0;local_socks[data.session] && s<local_socks[data.session].length;s++)
+				{
+					var socket=local_socks[data.session][s].sock;
+					if(socket.is_connected)
+					{
+						socket_send_local(data,socket);
+					}
+					else
+					{
+						s=delete_local_session(data.session,s);
+					}
+				}
+				break;
+		}
+	}
+	if(data_queue.length) data_queue=[];
+}
+function delete_local_session(session_id,index)
+{
+	log("local socket connection terminated: " + session_id);
+	local_socks[session_id].splice(index,1);
+	return index-1;
+}
+function socket_receive(socket)
+{
+	if(socket.data_waiting)
+	{
+		//store data in master array to be distributed later
+		var raw=socket.recvline(4092,connection_timeout);
+		var scope=raw.charAt(0);
+		var session_id=raw.substring(1,raw.indexOf(":"));
+		var data=raw.substr(raw.indexOf(":")+1);
+		
+		data_queue.push(new Packet(scope,session_id,socket.descriptor,data));
+	}
+}
+function socket_send_local(data,socket)
+{
+	if(socket.descriptor!=data.descriptor)
+	{
+		var d=data.data + "\r\n";
+		socket.write(d);
+	}
+}
+function socket_send_remote(data,socket)
+{
+	if(socket.descriptor!=data.descriptor)
+	{
+		var d=data.scope + data.session + ":" + data.data + "\r\n";
+		socket.write(d);
+	}
+}
+function delete_remote_session(index)
+{
+	log("remote socket connection terminated: " + remote_socks[index].sock.remote_ip_address);
+	remote_socks.splice(index,1);
+	return index-1;
+}
+function hub_connect()
+{
+	remote_hub=new Socket();
+	remote_hub.bind(0,server.interface_ip_address);
+	//if a central hub address is provided, attempt connection
+	for(var t=0;t<connection_attempts;t++)
+	{
+		if(remote_hub.connect(hub_address,hub_port,connection_timeout)) 
+		{
+			//send node identifier and bbs name so hub knows this is a distribution point
+			remote_hub.send(node_prefix + system.name + "\r\n");
+			
+			log("connection to " + hub_address + " successful");
+			return true;
+		}
+		mswait(50);
+	}
+	log("connection to " + hub_address + " failed with error " + remote_hub.error);
+	return false;
+}
+function hub_disconnect()
+{
+	log("disconnecting from main hub: " + hub_address);
+	remote_hub.close();
+}
+function store_socket(sock)
+{
+	//receive connection identifier from incoming socket connection (should always be first transmission)
+	var handshake=sock.recvline(4092,connection_timeout);
+	var identifier=handshake.charAt(0);
+	var session_id=handshake.substr(1);
+	
+	switch(identifier)
+	{
+		case node_prefix:
+			remote_socks.push(new Node(sock,session_id));
+			log("remote node connection from: " + session_id + "@" + sock.remote_ip_address);
+			break;
+		case local_prefix:
+			if(!local_socks[session_id]) local_socks[session_id]=[];
+			local_socks[session_id].push(new Session(sock));
+			log("local node connection: " + session_id);
+			break;
+		default:
+			log("unknown connection type");
+			sock.close();
+			break;
+	}
+}
+function count_local_sockets()
+{
+	var count=0;
+	for(var l in local_socks)
+	{
+		if(local_socks[l].length) count+=local_socks[l].length;
+	}
+	return count;
+}
+function count_remote_sockets()
+{
+	return remote_socks.length;
+}
+
+//TODO
+function find_user()
+{
+}
+function page_user()
+{
+}
+
+function Packet(scope,session,descriptor,data)
+{
+	this.session=session;
+	this.descriptor=descriptor;
+	this.data=data;
+	this.scope=scope;
+}
+function Node(socket,name)
+{
+	this.sock=socket;
+	this.name=name;
+}
+function Session(socket,session)
+{
+	this.sock=socket;
+	this.data=[];
+}
-- 
GitLab