all repos — felt @ 4f9ca53e1bfb24febbe981c746405c50194f9303

virtual tabletop for dungeons and dragons (and similar) using Go, MongoDB, and websockets

static/socket.js (raw)

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
110
111
112
113
let tableKey = {
  name: "",
  passcode: ""
}

let table = null;

let conn = null;

function showTableModal(show) {
  const modal = document.getElementById("table_modal");
  if (modal) {
    modal.style.display = show ? "block" : "none";
  }
}

function formatDice(r) {
  const date = new Date(r.timestamp)
  const p = document.createElement("p");
  p.innerHTML = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}<br>[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`;
  return p;
}

function logDice(dice, many) {
  const diceLog = document.getElementById("dice_log");
  if (!many) {
    dice = [ dice ];
  } else {
    if (diceLog) {
      diceLog.innerHTML = "";
    }
  }
  if (diceLog) {
      for(const r of dice) {
        const p = formatDice(r);
        diceLog.append(p);
        p.scrollIntoView();
      }
  }
}

function makeUpToDate(table) {
  if (table && table.diceRolls) {
    logDice(table.diceRolls, true);
  }
}

function dial() {
  // get tableKey from UI
  const tblNameInput = document.getElementById("input_table_name");
  const tblPassInput = document.getElementById("input_table_pass");
  if (tblNameInput && tblPassInput && tblNameInput.value && tblPassInput.value) {
    tableKey.name = tblNameInput.value;
    tableKey.passcode = tblPassInput.value;
    
    if (conn) {
      conn.close(1000);
    }
    conn = new WebSocket(`ws://${location.host}/subscribe`, `${tableKey.name}.${tableKey.passcode}`);
    conn.addEventListener("close", e => {
      if (e.code > 1001) {
        // TODO: add message to let user know they are reconnecting
        setTimeout(dial, 1000)
      } else {
        tabletop = document.getElementById("tabletop");
        if (tabletop) {
          tabletop.style.display = "none";
        }
        table = null;
      }
    });
    conn.addEventListener("open", e => {
      // TODO: add message to let user know they are at the table
      console.info("socket connected");
      tabletop = document.getElementById("tabletop");
      if (tabletop) {
        tabletop.style.display = "block";
      }
    });
    conn.addEventListener("error", e => {
      setErr(JSON.stringify(e));
      conn.close(1002);
    })
    
    conn.addEventListener("message", e => {
      const data = JSON.parse(e.data);
      if (table == null) {
        // first fetch comes from mongo, so the rolls array in each diceRoll is a byte array and needs to be decoded
        data.diceRolls.forEach(r=>{
          r.roll = Uint8Array.from(atob(r.roll), c => c.charCodeAt(0))
        })
        table = data;
        makeUpToDate(table);
      } else {
        if (data.diceRoll) {
          logDice(data.diceRoll);
        }
      }
      console.log(data);
    });
  }
}

async function publish(msg) {
  msg.key = tableKey;
  const res = await fetch('/publish', {
    method: 'POST',
    body: JSON.stringify(msg)
  });
  if (!res.ok) {
    setErr("Failed to publish message");
  }
}