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