diff options
| -rw-r--r-- | socket.js | 67 | 
1 files changed, 67 insertions, 0 deletions
diff --git a/socket.js b/socket.js new file mode 100644 index 0000000..4a030f5 --- /dev/null +++ b/socket.js @@ -0,0 +1,67 @@ +/* socket.js, a simple wrapper for a YWOT websocket connection */ + +class retryws extends EventEmitter{ // a wrapper on ws that retries on failure +  constructor(addr) { +    let sock; +    this.sockdown = true;  +    this.send = (message) => { +      if (sockdown){ +        throw "Socket is down!"; +      } +      sock.send(message); +    } +    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', () => { this.emit('open'); sockdown = false; }); +      sock.on('close', (err) => { setTimeout(sockinit,1000); sockdown = true; this.emit('close'); }); +      sock.on('error', (err) => { sock.close(); }); +      sock.on('message', (message) => { this.emit('message',message); }); +    } +  } +} + + +class Socket extends retryws { +  constructor(world='') { // Takes the name of the world, which can be an empty string. +    let loc = (name = '') ? '' : `/${name}`; +    let addr = `wss://www.yourworldoftext.com/${loc}ws`; +    super(addr); +    this.on('message', (msg)=>{ +      msg = JSON.parse(msg); +      switch (message.kind){ +        case 'write': +          this.emit('write', message.accepted, message.rejected); break; // A confirmation message for writes (accepted/rejected updates) +        case 'cursor': +          this.emit('cursor', message.positions, message.sender); break; // Must be used for self-identification +        case 'tileUpdate': +          this.emit('tileUpdate', message.sender, message.source, message.tiles); break; // Any change by another user or possibly oneself +        case 'fetch': +          this.emit('fetch', message.tiles); break; // The response to a fetch request +      } +    } + +    this.fetch = function(coords){ //coords is a list of quadruplets, each a min/max pair of y/x coordinate pairs which describes at most 1000 tiles +    //Unchecked for speed +      for (var i=0; i<coords.length; i++){ +        coords[i] = {"minY":coords[i][0], "minX":coords[i][1], "maxY":coords[i][2], "maxX":coords[i][3]}; +      } +      this.send(`{"fetchRectangles":${JSON.stringify(coords)},"kind":"fetch","v":"3"}`); +    } + +    this.write = function(chars){ //chars is an list of triplets [ tile coordinate (y/x), pixel coordinate (y/x < 16), char ] +      if (chars.length > 200){ +        throw "Too many characters to write"; +      } +      for (var i=0; i<chars.length; i++){ +        chars[i].splice(4,0,0); // +        chars[i].push(i); +      } +      this.send(`{"edits":${JSON.stringify(chars)},"kind":"write"}`; +    } +     +    this.cursor = function(coords){ //coords is just one quadruplet analagous to fetch; I think the api could handle more, but it's unnecessary for now. +      this.send(`"kind":"cursor","positions":[${JSON.stringify(coords)}]}`; +    } + +  } +}  | 
