Mercurial > hg > rlgwebd
comparison shterm.js @ 153:c4a32007d2dc
WebTTY: remove polling.
Communication now uses WebSockets only.
| author | John "Elwin" Edwards |
|---|---|
| date | Mon, 27 Jan 2014 16:02:27 -0800 |
| parents | 789c094675f4 |
| children |
comparison
equal
deleted
inserted
replaced
| 150:1d3cfe10974a | 153:c4a32007d2dc |
|---|---|
| 1 /* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it | 1 /* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it |
| 2 * is running a shell via the webtty.js server. | 2 * is running a shell via the webtty.js server. |
| 3 */ | 3 */ |
| 4 | 4 |
| 5 var isalive = false; // Whether the session is currently active. | 5 var isalive = false; // Whether the session is currently active. |
| 6 var nsend = 0; // The number of the next packet to send. | |
| 7 var nrecv = 0; // The next packet expected. | |
| 8 var msgQ = []; // Queue for out-of-order messages. | |
| 9 var conn = null; // WebSocket | 6 var conn = null; // WebSocket |
| 10 | |
| 11 // A state machine that keeps track of polling the server. | |
| 12 var ajaxstate = { | |
| 13 state: 0, | |
| 14 timerID: null, | |
| 15 clear: function () { | |
| 16 if (this.timerID != null) { | |
| 17 window.clearTimeout(this.timerID); | |
| 18 this.timerID = null; | |
| 19 } | |
| 20 }, | |
| 21 set: function (ms) { | |
| 22 this.clear(); | |
| 23 this.timerID = window.setTimeout(getData, ms); | |
| 24 }, | |
| 25 gotdata: function () { | |
| 26 this.set(100); | |
| 27 this.state = 0; | |
| 28 }, | |
| 29 gotnothing: function () { | |
| 30 if (this.state == 0) { | |
| 31 this.set(100); | |
| 32 this.state = 1; | |
| 33 } | |
| 34 else if (this.state == 1) { | |
| 35 this.set(300); | |
| 36 this.state = 2; | |
| 37 } | |
| 38 else if (this.state == 2) { | |
| 39 this.set(1000); | |
| 40 this.state = 3; | |
| 41 } | |
| 42 else { | |
| 43 this.set(5000); | |
| 44 this.state = 3; | |
| 45 } | |
| 46 }, | |
| 47 posted: function () { | |
| 48 this.set(100); | |
| 49 this.state = 0; | |
| 50 } | |
| 51 }; | |
| 52 | 7 |
| 53 function writeData(hexstr) { | 8 function writeData(hexstr) { |
| 54 var codenum; | 9 var codenum; |
| 55 var codes = []; | 10 var codes = []; |
| 56 var nc; | 11 var nc; |
| 117 } | 72 } |
| 118 termemu.write(codes); | 73 termemu.write(codes); |
| 119 return; | 74 return; |
| 120 } | 75 } |
| 121 | 76 |
| 122 function processMsg(response) { | |
| 123 if (response.t != "d" || typeof(response.d) != "string") | |
| 124 return; | |
| 125 if (response.n === nrecv) { | |
| 126 writeData(response.d); | |
| 127 nrecv++; | |
| 128 var next; | |
| 129 /* msgQ must be shifted every time nrecv is incremented, but the process | |
| 130 * stops whenever an empty space, corresponding to an unarrived message, | |
| 131 * is encountered. | |
| 132 */ | |
| 133 while ((next = msgQ.shift()) !== undefined) { | |
| 134 writeData(next.d); | |
| 135 nrecv++; | |
| 136 } | |
| 137 } | |
| 138 else if (response.n > nrecv) { | |
| 139 /* The current message comes after one still missing. Queue this one | |
| 140 * for later use. | |
| 141 */ | |
| 142 debug(1, "Got packet " + response.n + ", expected " + nrecv); | |
| 143 msgQ[response.n - nrecv - 1] = response; | |
| 144 } | |
| 145 else { | |
| 146 /* This message's number was encountered previously. */ | |
| 147 debug(1, "Discarding packet " + response.n + ", expected " + nrecv); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 function getData() { | |
| 152 if (!isalive) | |
| 153 return; | |
| 154 var datareq = new XMLHttpRequest(); | |
| 155 datareq.onreadystatechange = function () { | |
| 156 if (datareq.readyState == 4 && datareq.status == 200) { | |
| 157 var response = JSON.parse(this.responseText); | |
| 158 if (!response.t) | |
| 159 return; | |
| 160 else if (response.t == "E") { | |
| 161 if (response.c == 1) { | |
| 162 isalive = false; | |
| 163 debug(1, "Server error: " + response.s); | |
| 164 } | |
| 165 } | |
| 166 else if (response.t == "n") | |
| 167 ajaxstate.gotnothing(); | |
| 168 else if (response.t == "d") { | |
| 169 processMsg(response); | |
| 170 ajaxstate.gotdata(); | |
| 171 } | |
| 172 return; | |
| 173 } | |
| 174 }; | |
| 175 datareq.open('GET', '/feed', true); | |
| 176 datareq.send(null); | |
| 177 return; | |
| 178 } | |
| 179 | |
| 180 function postResponseHandler() { | |
| 181 if (this.readyState == 4 && this.status == 200) { | |
| 182 var response = JSON.parse(this.responseText); | |
| 183 if (!response.t || response.t == "n") | |
| 184 return; | |
| 185 else if (response.t == "E") { | |
| 186 if (response.c == 1) { | |
| 187 isalive = false; | |
| 188 debug(1, "Server error: " + response.s); | |
| 189 } | |
| 190 return; | |
| 191 } | |
| 192 else if (response.t != "d") | |
| 193 return; | |
| 194 /* It is a data message */ | |
| 195 if (response.d) { | |
| 196 processMsg(response); | |
| 197 //debug(1, "Got packet " + response.n); | |
| 198 } | |
| 199 ajaxstate.posted(); | |
| 200 return; | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 function sendback(str) { | 77 function sendback(str) { |
| 205 /* For responding to terminal queries. */ | 78 /* For responding to terminal queries. */ |
| 206 if (conn) { | 79 if (conn) { |
| 207 var msgObj = {"t": "d", "d": str}; | 80 var msgObj = {"t": "d", "d": str}; |
| 208 conn.send(JSON.stringify(msgObj)); | 81 conn.send(JSON.stringify(msgObj)); |
| 209 } | 82 } |
| 210 else { | 83 else { |
| 211 var formdata = {"t": "d", "n": nsend++, "d": str}; | 84 debug(1, "No connection: cannot send " + str); |
| 212 var datareq = new XMLHttpRequest(); | |
| 213 datareq.onreadystatechange = postResponseHandler; | |
| 214 datareq.open('POST', '/feed', true); | |
| 215 datareq.send(JSON.stringify(formdata)); | |
| 216 } | 85 } |
| 217 return; | 86 return; |
| 218 } | 87 } |
| 219 | 88 |
| 220 function sendkey(ev) { | 89 function sendkey(ev) { |
| 253 else { | 122 else { |
| 254 debug(1, "Ignoring keycode " + keynum); | 123 debug(1, "Ignoring keycode " + keynum); |
| 255 return; | 124 return; |
| 256 } | 125 } |
| 257 ev.preventDefault(); | 126 ev.preventDefault(); |
| 258 if (conn) { | 127 var msgObj = {"t": "d", "d": code}; |
| 259 var msgObj = {"t": "d", "d": code}; | 128 conn.send(JSON.stringify(msgObj)); |
| 260 conn.send(JSON.stringify(msgObj)); | |
| 261 } | |
| 262 else { | |
| 263 var formdata = {"t": "d", "n": nsend++, "d": code}; | |
| 264 var datareq = new XMLHttpRequest(); | |
| 265 datareq.onreadystatechange = postResponseHandler; | |
| 266 datareq.open('POST', '/feed', true); | |
| 267 datareq.send(JSON.stringify(formdata)); | |
| 268 } | |
| 269 //dkey(code); | 129 //dkey(code); |
| 270 return; | 130 return; |
| 271 } | 131 } |
| 272 | 132 |
| 273 var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c", | 133 var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c", |
| 318 keystr = kpkeys[c]; | 178 keystr = kpkeys[c]; |
| 319 } | 179 } |
| 320 else | 180 else |
| 321 return; | 181 return; |
| 322 //writeData("Sending " + keystr); | 182 //writeData("Sending " + keystr); |
| 323 if (conn) { | 183 var msgObj = {"t": "d", "d": keystr}; |
| 324 var msgObj = {"t": "d", "d": keystr}; | 184 conn.send(JSON.stringify(msgObj)); |
| 325 conn.send(JSON.stringify(msgObj)); | |
| 326 } | |
| 327 else { | |
| 328 var formdata = {"t": "d", "n": nsend++, "d": keystr}; | |
| 329 var datareq = new XMLHttpRequest(); | |
| 330 datareq.onreadystatechange = postResponseHandler; | |
| 331 datareq.open('POST', '/feed', true); | |
| 332 datareq.send(JSON.stringify(formdata)); | |
| 333 } | |
| 334 return; | 185 return; |
| 335 } | 186 } |
| 336 | 187 |
| 337 function setup() { | 188 function setup() { |
| 338 keyHexCodes.init(); | 189 keyHexCodes.init(); |
| 359 else | 210 else |
| 360 keydiv.className = "key"; | 211 keydiv.className = "key"; |
| 361 return; | 212 return; |
| 362 } | 213 } |
| 363 | 214 |
| 364 function loginWS(h, w) { | 215 function login(h, w) { |
| 365 if (conn) | 216 if (conn) |
| 366 return; | 217 return; |
| 218 if (!(window.WebSocket)) { | |
| 219 debug(1, "Cannot connect: WebSockets not supported"); | |
| 220 return; | |
| 221 } | |
| 367 var sockurl = "ws://" + window.location.host + "/sock?w=" + w + "&h=" + h; | 222 var sockurl = "ws://" + window.location.host + "/sock?w=" + w + "&h=" + h; |
| 368 conn = new WebSocket(sockurl); | 223 conn = new WebSocket(sockurl); |
| 369 conn.onopen = function (event) { | 224 conn.onopen = function (event) { |
| 370 isalive = true; | 225 isalive = true; |
| 371 setTitle("Logged in"); | 226 setTitle("Logged in"); |
| 391 debug(1, "WebSocket connection closed."); | 246 debug(1, "WebSocket connection closed."); |
| 392 setTitle("Not connected."); | 247 setTitle("Not connected."); |
| 393 } | 248 } |
| 394 } | 249 } |
| 395 | 250 |
| 396 function login(h, w) { | |
| 397 if (isalive) | |
| 398 return; | |
| 399 if (window.WebSocket) { | |
| 400 loginWS(h, w); | |
| 401 return; | |
| 402 } | |
| 403 params = {"login": true, "h": h, "w": w}; | |
| 404 var req = new XMLHttpRequest(); | |
| 405 req.onreadystatechange = function () { | |
| 406 if (req.readyState == 4 && req.status == 200) { | |
| 407 var logindict = JSON.parse(req.responseText); | |
| 408 if (logindict.login) { | |
| 409 /* Success */ | |
| 410 termemu.resize(logindict.h, logindict.w); | |
| 411 isalive = true; | |
| 412 nsend = 0; | |
| 413 nrecv = 0; | |
| 414 setTitle("Logged in"); | |
| 415 debug(1, "Logged in with id " + logindict.id); | |
| 416 getData(); | |
| 417 return; | |
| 418 } | |
| 419 return; | |
| 420 } | |
| 421 }; | |
| 422 req.open('POST', '/login', true); | |
| 423 req.send(JSON.stringify(params)); | |
| 424 //req.send("login=login&h=" + String(h) + "&w=" + String(w)); | |
| 425 return; | |
| 426 } | |
| 427 | |
| 428 function stop() { | 251 function stop() { |
| 429 if (conn) { | 252 if (conn) { |
| 430 conn.close(); | 253 conn.close(); |
| 431 return; | 254 } |
| 432 } | 255 else { |
| 433 var req = new XMLHttpRequest(); | 256 debug(1, "Cannot stop: connection already closed"); |
| 434 req.onreadystatechange = function () { | 257 } |
| 435 if (req.readyState == 4 && req.status == 200) { | |
| 436 /* Figure out whether or not it worked. */ | |
| 437 /* FIXME the server might respond with output. */ | |
| 438 isalive = false; | |
| 439 return; | |
| 440 } | |
| 441 }; | |
| 442 req.open('POST', '/feed', true); | |
| 443 req.send(JSON.stringify({"t": "q", "n": nsend++})); | |
| 444 return; | 258 return; |
| 445 } | 259 } |
| 446 | 260 |
| 447 function setTitle(tstr) { | 261 function setTitle(tstr) { |
| 448 var titlespan = document.getElementById("ttitle"); | 262 var titlespan = document.getElementById("ttitle"); |
