Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • dailybuild_linux-x64
  • dailybuild_win32
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • new_user_dat
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

presence-service.js

Blame
  • presence-service.js 7.85 KiB
    load('sbbsdefs.js');
    load('nodedefs.js');
    const imsg = load({}, 'sbbsimsg_lib.js');
    
    /* Override these defaults by adding corresponding keys to [presence_service]
     * in modopts.ini. All 'refresh' intervals are in milliseconds. Set 'local' or
     * 'remote' to false if you want to disable local or interBBS presence.
     */
    const defaultSettings = {
    	local: true,
    	remote: true, // Must set up instant messaging first: http://wiki.synchro.net/module:sbbsimsg
    	local_refresh: 5000, 
    	remote_refresh: 30000,
    	idle_local_refresh: 20000,
    	idle_remote_refresh: 120000,
    	remote_list_refresh: 3600000,
    };
    
    const settings = load('modopts.js', 'presence_service') || {};
    for (var s in defaultSettings) {
    	if (settings[s] === undefined) settings[s] = defaultSettings[s];
    }
    
    const clients = {};
    
    const sessions = {
    	local: { users: {} },
    	remote: {},
    };
    
    const intervals = {
    	localRefresh: null,
    	remoteRefresh: null,
    };
    
    function idle() {
    	return (Object.keys(clients).length < 1);
    }
    
    function broadcast(evt, data) {
    	if (idle()) return;
    	const sdata = JSON.stringify({ event: evt, data: data });
    	for (var c in clients) {
    		clients[c].once('write', (function (sdata) {
    			if (this.sendline !== undefined) { // client went away between queueing and running this callback? Alls I knows is that this.sendline may be undefined here.
    				this.sendline(sdata);
    			}
    		}).bind(clients[c], sdata)); // bind because otherwise 'sdata is undefined'
    	}
    }
    
    function getLocalSessions() {
    	
    	const users = {};
    	
    	const u = new User(1);
    	system.node_list.forEach(function (e, i) {
    		if (e.status !== NODE_INUSE) return;
    		u.number = e.useron;
    		if (!users[u.alias]) {
    			users[u.alias] = {
    				name: u.alias,
    				gender: u.gender,
    				age: u.age,
    				location: u.location,
    				xtrn: xtrn_area.prog[u.curxtrn].name,
    				sessions: {},
    			};
    		}
    		users[u.alias].sessions[i] = { action: NodeAction[e.action] };
    	});
    
    	directory(system.data_dir + 'user/*.web').forEach(function (e) {
    		if (time() - file_date(e) >= 900) return;
    		const f = new File(e);
    		if (!f.open('r')) return;
    		const session = f.iniGetObject();
    		f.close();
    		const base = file_getname(e).replace(file_getext(e), '');
    		const un = parseInt(base, 10);
    		if (isNaN(un) || un < 1 || un > system.lastuser) return;
    		u.number = un;
    		if (!users[u.alias]) {
    			users[u.alias] = {
    				name: u.alias,
    				gender: u.gender,
    				age: u.age,
    				location: u.location,
    				xtrn: xtrn_area.prog[u.curxtrn].name,
    				sessions: {},
    			};
    		}
    		users[u.alias].sessions.W = { action: session.action };
    	});
    
    	return users;
    
    }
    
    function scanLocal() {
    
    	if (!settings.local) return;
    
    	const current = getLocalSessions();
    
    	for (var u in sessions.local.users) {
    		for (var s in sessions.local.users[u].sessions) {
    			if (current[u] === undefined || current[u].sessions[s] === undefined) {
    				delete sessions.local.users[u].sessions[s];
    				broadcast('logoff', {
    					user: u,
    					session: s,
    				});
    			} else {
    				var update = false;
    				if (current[u].xtrn !== sessions.local.users[u].xtrn) {
    					sessions.local.users[u].xtrn = current[u].xtrn;
    					update = true;
    				}
    				if (current[u].sessions[s].action !== sessions.local.users[u].sessions[s].action) {
    					sessions.local.users[u].sessions[s].action = current[u].sessions[s].action;
    					update = true;
    				}
    				if (update) {
    					broadcast('update', {
    						user: sessions.local.users[u],
    						session: s,
    					});
    				}
    				delete current[u].sessions[s];
    			}
    		}
    		if (Object.keys(sessions.local.users[u].sessions).length < 1) delete sessions.local.users[u];
    		if (current[u] !== undefined && current[u].sessions !== undefined && Object.keys(current[u].sessions).length < 1) delete current[u];
    	}
    
    	for (var u in current) {
    		if (sessions.local.users[u] === undefined) sessions.local.users[u] = current[u];
    		for (var s in current[u].sessions) {
    			sessions.local.users[u].sessions[s] = current[u].sessions[s];
    			broadcast('logon', {
    				user: sessions.local.users[u],
    				session: s,
    			});
    		}
    	}
    
    }
    
    function getRemoteSessions() {
    	if (settings.remote) imsg.request_active_users();
    }
    
    function onRemoteLogon(usr, sys) {
    
    	if (!settings.remote) return;
    
    	if (sys === null) return; // Only the first result contains system details
    
    	if (!sessions.remote[sys.host]) {
    		sessions.remote[sys.host] = {
    			host: sys.host,
    			name: sys.name,
    			users: {}
    		};
    		broadcast('system', {
    			host: sys.host,
    			name: sys.name
    		});
    	}
    
    	sys.users.forEach(function (e) {
    		if (!sessions.remote[sys.host].users[e.name]) {
    			sessions.remote[sys.host].users[e.name] = {
    				name: e.name,
    				gender: e.sex,
    				age: e.age,
    				location: e.location,
    				xtrn: e.xtrn,
    				sessions: {},
    			};
    		}
    		const skey = e.prot === 'web' ? 'W' : e.node;
    		if (!sessions.remote[sys.host].users[e.name].sessions[skey]) {
    			sessions.remote[sys.host].users[e.name].sessions[skey] = { action: e.action };
    			broadcast('logon', {
    				system: sys.name,
    				host: sys.host,
    				user: sessions.remote[sys.host].users[e.name],
    				session: skey,
    			});
    		} else if (sessions.remote[sys.host].users[e.name].sessions[skey].action !== e.action) {
    			sessions.remote[sys.host].users[e.name].sessions[skey].action = e.action;
    			broadcast('update', {
    				system: sys.name,
    				host: sys.host,
    				user: sessions.remote[sys.host].users[e.name],
    				session: skey,
    			});
    		}
    	});
    
    }
    
    function onRemoteLogoff(usr, sys) {
    	if (!settings.remote) return;
    	if (!sessions.remote[sys.host]) return;
    	if (!sessions.remote[sys.host].users[usr.name]) return;
    	const skey = usr.prot === 'web' ? 'W' : usr.node;
    	if (!sessions.remote[sys.host].users[usr.name].sessions[skey]) return;
    	delete sessions.remote[sys.host].users[usr.name].sessions[skey];
    	if (!Object.keys(sessions.remote[sys.host].users[usr.name].sessions).length) delete sessions.remote[sys.host].users[usr.name];
    	broadcast('logoff', {
    		system: sys.name,
    		host: sys.host,
    		user: usr.name,
    		session: skey,
    	});
    }
    
    function refresh(idling) {
    	if (settings.local) {
    		if (intervals.localRefresh !== null) js.clearInterval(intervals.localRefresh);
    		js.setInterval(scanLocal, idling ? settings.idle_local_refresh : settings.local_refresh);
    		js.setImmediate(scanLocal);
    	}
    	if (settings.remote) {
    		if (intervals.remoteRefresh !== null) js.clearInterval(intervals.remoteRefresh);
    		js.setInterval(getRemoteSessions, idling ? settings.idle_remote_refresh : settings.remote_refresh);
    		js.setImmediate(getRemoteSessions);
    	}
    }
    
    function onRemoteData() {
    	const msg = imsg.receive_active_users();
    	imsg.parse_active_users(msg, onRemoteLogon, onRemoteLogoff);
    }
    
    function onClient() {
    	const sock = this.accept();
    	if (!sock) {
    		log(LOG_DEBUG, '!Error accepting connection: ' + sock.error);
    		return;
    	}
    	if (idle()) refresh(false);
    	clients[sock.descriptor] = sock;
    	sock.once('read', function () {
    		if (this.is_connected) this.close();
    		delete clients[this.descriptor];
    		if (idle()) refresh(true);
    	});
    	sock.once('write', function () {
    		this.sendline(JSON.stringify({ event: 'state', data: sessions }));
    	});
    }
    
    if (this.server === undefined) { // jsexec
    
    	var port = 10011;
    	var addr = [ '0.0.0.0', '::' ];
    
    	for (arg = 0; arg < argc; arg++) {
    		switch (argv[arg].toLowerCase()) {
    			case "-p":
    				port = parseInt(argv[++arg]);
    				break;
    			case "-a":
    				addr = argv[++arg].split(',');
    				break;
    			default:
    				break;
    		}
    	}
    
    	const server = { socket: new ListeningSocket(addr, port, "Presence") };
    	if (!server.socket) exit();
    
    }
    
    server.socket.on('read', onClient);
    imsg.sock.on('read', onRemoteData);
    
    if (settings.remote) {
    	/* This service may run indefinitely, so we should reload the BBS list once in
    	 * a while so we'll pick up new systems to poll and drop any old ones.
    	 */
    	js.setInterval(imsg.read_sys_list.bind(imsg), settings.remote_list_refresh); // bind because otherwise 'this.sys_list is undefined'
    	js.setImmediate(imsg.read_sys_list.bind(imsg)); // bind because otherwise 'this.sys_list is undefined'
    }
    
    refresh(true); // Start in 'idle' mode, set refresh timers, do an initial poll
    
    js.do_callbacks = true;