/* socket.js, a simple wrapper for a YWOT websocket connection */ const ws = require('ws') const EventEmitter = require('events'); let mod = (div, end) => ( ( (div % end) + end ) % end ) // Makes it such that (-2) % 3 = 1 instead of -2. class retryws extends EventEmitter{ // a wrapper on ws that retries on failure constructor(addr) { super(); let sock; let sockdown = true; this.send = (message) => { if (sockdown){ throw "Socket is down!"; } sock.send(message); } let self = this; function sockinit(){ // addr is not expected to change, so not handed as input; similarly, sock is not passed in a chain because behavior is expected to be serial (either initializing or retrying) sock = new ws(addr); sock.on('open', () => { sockdown = false; self.emit('open'); }); sock.on('close', (err) => { console.log(err); setTimeout(sockinit,1000); sockdown = true; self.emit('close'); }); sock.on('error', (err) => { console.log(err); sock.close(); }); sock.on('message', (message) => { self.emit('message',message);}); } sockinit(); } } class Socket extends retryws { constructor(world='') { // Takes the name of the world, which can be an empty string. let loc = (world == '') ? '' : `${world}/`; let addr = `wss://www.yourworldoftext.com/${loc}ws/`; super(addr); let writect = 0; // A running counter to make the server write confirmations meaningful this.on('message', (msg)=>{ msg = JSON.parse(msg); switch (msg.kind){ case 'write': this.emit('write', msg.accepted, msg.rejected); break; // A confirmation message for writes (accepted/rejected updates) case 'cursor': this.emit('cursor', msg.positions.map(pos => [pos.tileY*8+pos.charY,pos.tileX*16+pos.charX]), msg.sender); break; // Must be used for self-identification case 'tileUpdate': this.emit('tileUpdate', msg.sender, msg.source, msg.tiles); break; // Any change by another user or possibly oneself case 'fetch': this.emit('fetch', msg.tiles); break; // The response to a fetch request } }); this.fetch = function(coords){ //coords is a list of min/max pairs of y/x coordinate pairs which each describe at most 1000 tiles //Unchecked for speed for (var i=0; i 200) throw "Too many characters to write by server decree"; if (chars.length == 0) throw "Send at least a character."; let write = []; for (let cha of chars){ let coord = cha[0]; write.push([ Math.floor(coord[0]/8), Math.floor(coord[1]/16), mod(coord[0], 8), mod(coord[1], 16), 0, cha[1], writect ]); writect++; } this.send( JSON.stringify({ "edits": write, "kind": "write" })); return writect-1; // Last label within write for cross-referencing with confirmation } this.cursor = function(coords){ //coords is just one pair of char coords; I think the api could handle more, but it's unnecessary for now. this.send( JSON.stringify({ "kind": "cursor", "positions": [ { "tileY": Math.floor(coords[0]/8), "tileX": Math.floor(coords[1]/16), "charY": coords[0] % 8, "charX": coords[1] % 16 } ] })); } } } module.exports = Socket;