aboutsummaryrefslogtreecommitdiff
path: root/socket.js
blob: f2502e8c2938f4dfc722e581c8b9113e3a40c7d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* socket.js, a simple wrapper for a YWOT websocket connection */
const ws = require('ws')
const EventEmitter = require('events');

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);
    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 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( JSON.stringify({
        "fetchRectangles": coords,
        "kind": "fetch",
        "v": "3"
      }));
    }

    this.write = function(chars){ //chars is an list of pairs [ pixel coordinate (y/x < 16), char ]
      if (chars.length > 200){
        throw "Too many characters to write";
      }
      for (var i=0; i<chars.length; i++){
        let char = chars[i]
        let coord = char[0];
        chars[i] = [Math.floor(coord[0]/8),Math.floor(coord[1]/16),(coord[0] % 8),(coord[1] % 16),char[1]];
        chars[i].splice(4,0,0);
        chars[i].push(i);
      }
      this.send( JSON.stringify({
        "edits": chars,
        "kind": "write"
      }));
    }
    
    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;