UNPKG

@zimjs/socket

Version:

Multiuser CLient with Socket.IO for the ZIM JavaScript Canvas Framework. Also see @zimjs/socket-server if not using the ZIM Server for the server-side.

433 lines (359 loc) 12.7 kB
// ZIM - https://zimjs.com - Code Creativity! // JavaScript Canvas Framework for General Interactive Media // free to use - donations welcome of course! https://zimjs.com/donate // ZIM SOCKET - see https://zimjs.com/socket for examples and server info // ~~~~~~~~~~~~~~~~~~~~~~~~ // DESCRIPTION - coded in 2015 (c) ZIM // zimsocket.js provides code for multiuser sockets to share properties // a client sends properties and receives objects of others' properties // requires socket.io and a server running NodeJS and zimserver.js // The Socket Module has Socket() // DOCS // Docs are provided at https://zimjs.com/docs.html // See SOCKET MODULE at bottom // ~~~~~~~~~~~~~~~~~~~~~~~~ // ES6 module - see https://zimjs.com/es6 // or see https://zimjs.com/code for more options // NOTE: MUST FIRST IMPORT SOCKETIO with SCRIPT TAGS // For instance: https://zimjs.org/cdn/2.3.0/socket.io.js import {io} from "socket.io-client"; import zim from "zimjs"; import {zimSocket2URL} from "https://zimjs.org/cdn/zimserver_urls_module"; // needs to be from ZIM server zim.zimSocketURL = zimSocket2URL; var WW = window||{}; if (!WW.zns) WW.zimSocketURL = zim.zimSocketURL; zim.EventDispatcher = function(target) { this.listeners = {}; this.target = target; var that = this; this.addEventListener = function (type, listener) { if (!that.listeners[type]) { that.listeners[type] = []; } that.listeners[type].push(listener); } this.removeEventListener = function (type, listener) { var listenList = that.listeners[type]; for (var i=0; i<listenList.length; i++) { if (listenList[i] === listener) { listenList.splice(i, 1); } } } this.removeAllEventListeners= function() { this.listeners = {}; } this.dispatchEvent = function (type, params) { var listenList = that.listeners[type]; var success = false; if (listenList) { for (var i=0; i<listenList.length; i++) { try { listenList[i].call(that, params); success = true; } catch (e) { zog("ZIM DispatchEvent() error: " + type + " " + e); } } } return success; } this.on = this.addEventListener; this.off = this.removeEventListener; this.offAll = this.removeAllEventListeners; } zim.Socket = function(server, appName, roomName, maxPeople, fill, initObj) { zim.EventDispatcher.call(this, this); if (zot(server)) server = this.server = "http://localhost:3000/"; if (zot(appName) || appName == "") {zog("zim socket - Socket():\nmust provide app name"); return;} this.ready = false; var that = this; var noSocket = "zim socket - Socket(): sorry no socket"; var socket; var data; var historyString; var current; // {id:{id:"a2s2d24fa2sd", x:20, y:30}, id2:{id2:831kh4597kh, x:22, y:66}} var last; // {id:lastUpdaterID, properties:{text:[id,"hi"], x:[id2,100], y:[id3,"10"]}} var my; // {id:"asfwer3231dsa", x:22, y:44, text:"cool"} function connect() { socket = that.socket = io.connect(server); } connect(); socket.on("connect", function() { addEventListeners(); joinRoom(appName, roomName, maxPeople, fill, initObj); }); function addEventListeners() { socket.on("join", join); socket.on("receive", receive); socket.on("time", time); socket.on("sync", sync); socket.on("otherleave", otherLeave); socket.on("connect_error", connectError); socket.on("disconnect", disconnect); } function joinRoom(appName, roomName, maxPeople, fill, initObj) { if (zot(appName) || appName == "") {zog("zim socket - Socket():\nmust provide app name"); return;} appName = that.applicationName = appName.replace(/\s/, "").toLowerCase(); if (roomName) {that.roomName = roomName.replace(/\s/, "").toLowerCase();} // can be null if (zot(maxPeople)) maxPeople = that.maxPeople = 0; if (zot(fill)) fill = that.fill = true; // fills in room if people leave data = {appName:appName, roomName:roomName, maxPeople:maxPeople, fill:fill, initObj:initObj}; my = initObj || {}; socket.emit('join', data); } // -------------- EVENTS --------------- function join(data) { // event when this person joins a room that.masterTime = data.masterTime; that.joinTime = data.joinTime; historyString = data.history; last = data.last; current = data.current; that.id = my.id = data.id; that.master = (that.size==0); if (!that.ready) { that.dispatchEvent("ready", current); that.ready = true; } else { that.dispatchEvent("roomchange", current); } } function receive(data, type) { if (data && data.zimMaster) { that.master = (that.id == data.zimMaster); return; } // event for receiving data from another person var id = that.senderID = data.id; // update current values current[id] = zim.merge(current[id], data); // update last information (responsible for keeping last up to date) last.id = id; for (var i in data) { last.properties[i] = [id, data[i]]; } if (type == "message") { that.dispatchEvent("data", data); // for other people's } else if (type == "join") { that.lastJoinID = id; that.dispatchEvent("otherjoin", data); } } function time(data) { // event for receiving time (masterTime and currentTime) data.joinTime = that.joinTime; that.dispatchEvent("time", data); } function sync(data) { // event for syncing data that.masterTime = data.masterTime; that.joinTime = data.joinTime; historyString = data.history; last = data.last; current = data.current; that.id = my.id = data.id; that.dispatchEvent("sync", data); } function otherLeave(id) { // event when another person leaves the room that.lastLeaveID = id; that.dispatchEvent("otherleave", current[id]); // data is removed as other leaves so update current and last // after we dispatch otherleave event to give chance to see their data delete current[id]; // note, the leaving client's id is still kept in the last data } function connectError() { that.ready = false; that.dispatchEvent("error"); removeEventListeners(); } function disconnect() { if (that.master && that.size > 0) { for (var id in current) { socket.emit('message', {zimMaster: id}); break; } } socket.emit('message', {disconnect: {master:that.master, size:that.size, current:current}}); that.ready = false; that.dispatchEvent("disconnect"); removeEventListeners(); } WW.onbeforeunload = disconnect; // -------------- MISC --------------- function removeEventListeners() { socket.removeEventListener("receive", receive); socket.removeEventListener("join", join); socket.removeEventListener("time", time); socket.removeEventListener("sync", sync); socket.removeEventListener("otherleave", otherLeave); socket.removeEventListener("connect_error", connectError); socket.removeEventListener("disconnect", disconnect); } // various socketio connect errors are not firing for me var connectionTries = 0; var timer = WW.setInterval(function() { if (socket.connected) { WW.clearInterval(timer); } else { connectionTries++; if (connectionTries > 4) { WW.clearInterval(timer); that.dispatchEvent("error"); socket.disconnect(); } else { connect(); } } }, 1000); // -------------- PROPERTIES --------------- Object.defineProperty(this, 'history', { get: function() { return historyString; }, set: function(value) { zog("zim socket - Socket(): history is read only (try appendToHistory() and clearHistory() methods)"); } }); Object.defineProperty(this, 'size', { get: function() { var count = 0; for (var i in current) count++; return count; }, set: function(value) { zog("zim socket - Socket(): size is read only (perhaps see maxPeople)"); } }); // -------------- METHODS --------------- this.changeRoom = function(appName, roomName, maxPeople, fill, initObj) { joinRoom(appName, roomName, maxPeople, fill, initObj); // will trigger a ZIM Socket ready event for this client // will trigger a ZIM Socket otherleave and otherjoin events for other clients } this.requestTime = function() { socket.emit('time'); // will trigger a ZIM Socket time event } this.requestSync = function() { socket.emit('sync'); // will trigger a ZIM Socket sync event } // SETTING YOUR PROPERTIES this.setProperty = function(propertyName, propertyValue) { if (!socket) {zog(noSocket); return;} if (zot(propertyName)) {zog("zim socket - Socket.setProperty(): please enter property name"); return;} var object = {}; object[propertyName] = propertyValue; my[propertyName] = propertyValue; last.id = that.id; last.properties[propertyName] = [that.id, propertyValue]; socket.emit('message', object); // will trigger a ZIM Socket data event for other clients in the room } this.setProperties = function(object) { if (!socket) {zog(noSocket); return;} if (zot(object) || typeof object !== 'object' || Array.isArray(object)) {zog("zim socket - Socket.setProperties(): please enter object of properties"); return;} my = zim.merge(my, object); for (var i in object) { last.properties[i] = [that.id, object[i]]; } socket.emit('message', object); // will trigger a ZIM Socket data event for other clients in the room } // GETTING PROPERTIES AND PROPERTY OBJECTS this.getMyProperty = function(propertyName) { // gets your own value for property name return my[propertyName]; } this.getMyData = function() { // gets your own data object return my; } this.getOtherProperty = function(id, propertyName) { // gets another client's value for property name if (!current || !current[id]) return; return current[id][propertyName]; } this.getOtherData = function(id) { // gets another client's object of properties if (current) return current[id]; } this.getSenderProperty = function(propertyName) { // gets sender client's value for property name if (!that.senderID) return; if (!current[that.senderID]) return; return current[that.senderID][propertyName]; } this.getSenderData = function() { // gets sender client's object of properties if (!that.senderID) return; return current[that.senderID]; } this.getProperties = function(propertyName) { // returns a array of values for the propertyName of others - for x we might get [12, 14, 33, etc.] if (zot(propertyName)) return; var list = []; var val; for (var i in current) { val = current[i][propertyName]; if (val) list.push(val); } return list; } this.getData = function() { // returns object of all client data (not own) return current; } // LATEST this.getLatestValue = function(propertyName) { // gets latest value of a property if (zot(last.properties[propertyName]) || last.properties[propertyName] == "undefined") return null; return last.properties[propertyName][1]; } this.getLatestTime = function(propertyName) { // gets latest time of a property if (zot(last.properties[propertyName])) return undefined; return last.properties[propertyName][2]; } this.getLatestValueID = function(propertyName) { // gets id of latest updater for a property if (zot(last.properties[propertyName])) return undefined; return last.properties[propertyName][0]; } this.getLatestProperties = function() { // gets an array of the last properties (names) sent - could be one or more var list = []; for (var property in last.properties) { if (last.properties[property][0] == last.id) { list.push(property); } } return list; } // HISTORY this.appendToHistory = function(someText) { // sends text to be stored on history // need to send \n as well if desired // only used for new people to join if (!socket) {zog(noSocket); return;} socket.emit('history', someText); } this.clearHistory = function() { if (!socket) {zog(noSocket); return;} historyString = ""; socket.emit('clearhistory'); } // TO DISCONNECT this.dispose = function() { socket.disconnect(); removeEventListeners(); current = null; last = null; // will trigger a ZIM Socket otherleave event for other clients in the room } } zim.Socket.prototype = new zim.EventDispatcher(); zim.Socket.prototype.constructor = zim.Socket; if (!WW.zns) WW.Socket = zim.Socket; export const Socket = zim.Socket; export const zimSocketURL = zim.zimSocketURL;