aboutsummaryrefslogtreecommitdiff
path: root/socket.js
blob: 3d8e63bd523a08753c1aff1b2f7fdc1d56698643 (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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* 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();
    this._sock;
    this._sockdown = true; 
    this._addr = addr;
    this.sockinit();
  }
  send(message){
    if (this._sockdown){
      throw "Socket is down!";
    }
    this._sock.send(message);
  }
  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)
    let self = this;
    this._sock = new ws(this._addr);
    this._sock.on('open', () => { self._sockdown = false; self.emit('open'); });
    this._sock.on('close', (err) => { console.log('close', err);
    setTimeout(self.sockinit,1000); self._sockdown = true; self.emit('close'); });
    this._sock.on('error', (err) => { console.log('error', err); self._sock.close(); });
    this._sock.on('message', (message) => { self.emit('message',message);});
  }

}

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._writect = 0; // A running counter to make the server write confirmations meaningful
    this.on('message', this._msgparse);
  }
  _msgparse(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
    }
  }

  fetch(coords){ //coords is a list of min/max pairs of y/x coordinate pairs which each describe at most 1000 tiles
    for (let i=0; i<coords.length; i++){
      let c = coords[i];
      if ( (c[1][0]-c[0][0]+1) * (c[1][1]-c[0][1]+1) > 1000) throw "Fetch has overlarge rectangular request";
      coords[i] = {"minY":c[0][0], "minX":c[0][1], "maxY":c[1][0], "maxX":c[1][1]};
    }
    this.send( JSON.stringify({
      "fetchRectangles": coords,
      "kind": "fetch",
      "v": "3"
    }));
  }

  write(chars){ //chars is an list of pairs [ pixel coordinate (y/x < 16), char ]
    if (chars.length > 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],
        this._writect
      ]);
      this._writect++;
    }
    this.send( JSON.stringify({
      "edits": write,
      "kind": "write"
    }));
    return this._writect-1; // Last label within write for cross-referencing with confirmation
  }
    
  cursor(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;