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");