mirror of
https://github.com/fcwu/docker-ubuntu-vnc-desktop
synced 2026-08-05 16:12:41 +02:00
Push submodule websockify of noVNC
This commit is contained in:
@@ -0,0 +1,919 @@
|
||||
// VT100.js -- a text terminal emulator in JavaScript with a ncurses-like
|
||||
// interface and a POSIX-like interface. (The POSIX-like calls are
|
||||
// implemented on top of the ncurses-like calls, not the other way round.)
|
||||
//
|
||||
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
|
||||
//
|
||||
// 2007-08-12 - refresh():
|
||||
// - factor out colour code to html_colours_()
|
||||
// - fix handling of A_REVERSE | A_DIM
|
||||
// - simplify initial <br /> output code
|
||||
// - fix underlining colour
|
||||
// - fix attron() not to turn off attributes
|
||||
// - decouple A_STANDOUT and A_BOLD
|
||||
// 2007-08-11 - getch() now calls refresh()
|
||||
// 2007-08-06 - Safari compat fix -- turn '\r' into '\n' for onkeypress
|
||||
// 2007-08-05 - Opera compat fixes for onkeypress
|
||||
// 2007-07-30 - IE compat fixes:
|
||||
// - change key handling code
|
||||
// - add <br />...<br /> so that 1st and last lines align
|
||||
// 2007-07-28 - change wrapping behaviour -- writing at the right edge no
|
||||
// longer causes the cursor to immediately wrap around
|
||||
// - add <b>...</b> to output to make A_STANDOUT stand out more
|
||||
// - add handling of backspace, tab, return keys
|
||||
// - fix doc. of VT100() constructor
|
||||
// - change from GPL to LGPL
|
||||
// 2007-07-09 - initial release
|
||||
//
|
||||
// class VT100
|
||||
// A_NORMAL, A_UNDERLINE, A_REVERSE, A_BLINK, A_DIM, A_BOLD, A_STANDOUT
|
||||
// =class constants=
|
||||
// Attribute constants.
|
||||
// VT100(wd, ht, scr_id) =constructor=
|
||||
// Creates a virtual terminal with width `wd', and
|
||||
// height `ht'. The terminal will be displayed between
|
||||
// <pre>...</pre> tags which have element ID `scr_id'.
|
||||
// addch(ch [, attr])
|
||||
// Writes out the character `ch'. If `attr' is given,
|
||||
// it specifies the attributes for the character,
|
||||
// otherwise the current attributes are used.
|
||||
// addstr(stuff) Writes out the string `stuff' using the current
|
||||
// attributes.
|
||||
// attroff(mode) Turns off any current options given in mode.
|
||||
// attron(mode) Turns on any options given in mode.
|
||||
// attrset(mode) Sets the current options to mode.
|
||||
// bkgdset(attr) Sets the background attributes to attr.
|
||||
// clear() Clears the terminal using the background attributes,
|
||||
// and homes the cursor.
|
||||
// clrtobol() Clears the portion of the terminal from the cursor
|
||||
// to the bottom.
|
||||
// clrtoeol() Clears the portion of the current line after the
|
||||
// cursor.
|
||||
// curs_set(vis [, grab])
|
||||
// If `vis' is 0, makes the cursor invisible; otherwise
|
||||
// make it visible. If `grab' is given and true, starts
|
||||
// capturing keyboard events (for `getch()'); if given
|
||||
// and false, stops capturing events.
|
||||
// echo() Causes key strokes to be automatically echoed on the
|
||||
// terminal.
|
||||
// erase() Same as `clear()'.
|
||||
// getch(isr) Arranges to call `isr' when a key stroke is
|
||||
// received. The received character and the terminal
|
||||
// object are passed as arguments to `isr'.
|
||||
// getmaxyx() Returns an associative array with the maximum row
|
||||
// (`y') and column (`x') numbers for the terminal.
|
||||
// getyx() Returns an associative array with the current row
|
||||
// (`y') and column (`x') of the cursor.
|
||||
// move(r, c) Moves the cursor to row `r', column `c'.
|
||||
// noecho() Stops automatically echoing key strokes.
|
||||
// refresh() Updates the display.
|
||||
// scroll() Scrolls the terminal up one line.
|
||||
// standend() Same as `attrset(VT100.A_NORMAL)'.
|
||||
// standout() Same as `attron(VT100.A_STANDOUT)'.
|
||||
// write(stuff) Writes `stuff' to the terminal and immediately
|
||||
// updates the display; (some) escape sequences are
|
||||
// interpreted and acted on.
|
||||
|
||||
// constructor
|
||||
function VT100(wd, ht, scr_id)
|
||||
{
|
||||
var r;
|
||||
var c;
|
||||
var scr = document.getElementById(scr_id);
|
||||
this.wd_ = wd;
|
||||
this.ht_ = ht;
|
||||
this.scrolled_ = 0;
|
||||
this.bkgd_ = {
|
||||
mode: VT100.A_NORMAL,
|
||||
fg: VT100.COLOR_WHITE,
|
||||
bg: VT100.COLOR_BLACK
|
||||
};
|
||||
this.c_attr_ = {
|
||||
mode: VT100.A_NORMAL,
|
||||
fg: VT100.COLOR_WHITE,
|
||||
bg: VT100.COLOR_BLACK
|
||||
};
|
||||
this.text_ = new Array(ht);
|
||||
this.attr_ = new Array(ht);
|
||||
for (r = 0; r < ht; ++r) {
|
||||
this.text_[r] = new Array(wd);
|
||||
this.attr_[r] = new Array(wd);
|
||||
}
|
||||
this.scr_ = scr;
|
||||
this.cursor_vis_ = true;
|
||||
this.grab_events_ = false;
|
||||
this.getch_isr_ = undefined;
|
||||
this.key_buf_ = [];
|
||||
this.echo_ = true;
|
||||
this.esc_state_ = 0;
|
||||
// Internal debug setting.
|
||||
this.debug_ = 0;
|
||||
this.clear();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
// public constants -- colours and colour pairs
|
||||
VT100.COLOR_BLACK = 0;
|
||||
VT100.COLOR_BLUE = 1;
|
||||
VT100.COLOR_GREEN = 2;
|
||||
VT100.COLOR_CYAN = 3;
|
||||
VT100.COLOR_RED = 4;
|
||||
VT100.COLOR_MAGENTA = 5;
|
||||
VT100.COLOR_YELLOW = 6;
|
||||
VT100.COLOR_WHITE = 7;
|
||||
VT100.COLOR_PAIRS = 256;
|
||||
VT100.COLORS = 8;
|
||||
// public constants -- attributes
|
||||
VT100.A_NORMAL = 0;
|
||||
VT100.A_UNDERLINE = 1;
|
||||
VT100.A_REVERSE = 2;
|
||||
VT100.A_BLINK = 4;
|
||||
VT100.A_DIM = 8;
|
||||
VT100.A_BOLD = 16;
|
||||
VT100.A_STANDOUT = 32;
|
||||
VT100.A_PROTECT = VT100.A_INVIS = 0; // ?
|
||||
// other public constants
|
||||
VT100.TABSIZE = 8;
|
||||
// private constants
|
||||
VT100.ATTR_FLAGS_ = VT100.A_UNDERLINE | VT100.A_REVERSE | VT100.A_BLINK |
|
||||
VT100.A_DIM | VT100.A_BOLD | VT100.A_STANDOUT |
|
||||
VT100.A_PROTECT | VT100.A_INVIS;
|
||||
VT100.COLOR_SHIFT_ = 6;
|
||||
VT100.browser_ie_ = (navigator.appName.indexOf("Microsoft") != -1);
|
||||
VT100.browser_opera_ = (navigator.appName.indexOf("Opera") != -1);
|
||||
// class variables
|
||||
VT100.the_vt_ = undefined;
|
||||
|
||||
// class methods
|
||||
|
||||
// this is actually an event handler
|
||||
VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event)
|
||||
{
|
||||
var vt = VT100.the_vt_, ch;
|
||||
if (vt === undefined)
|
||||
return true;
|
||||
if (VT100.browser_ie_ || VT100.browser_opera_) {
|
||||
ch = event.keyCode;
|
||||
if (ch == 13)
|
||||
ch = 10;
|
||||
else if (ch > 255 || (ch < 32 && ch != 8))
|
||||
return true;
|
||||
ch = String.fromCharCode(ch);
|
||||
} else {
|
||||
ch = event.charCode;
|
||||
//dump("ch: " + ch + "\n");
|
||||
//dump("ctrl?: " + event.ctrlKey + "\n");
|
||||
vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode);
|
||||
if (ch) {
|
||||
if (ch > 255)
|
||||
return true;
|
||||
if (event.ctrlKey && event.shiftKey) {
|
||||
// Don't send the copy/paste commands.
|
||||
var charStr = String.fromCharCode(ch);
|
||||
if (charStr == 'C' || charStr == 'V') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey) {
|
||||
ch = String.fromCharCode(ch - 96);
|
||||
} else {
|
||||
ch = String.fromCharCode(ch);
|
||||
if (ch == '\r')
|
||||
ch = '\n';
|
||||
}
|
||||
} else {
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_BACK_SPACE:
|
||||
ch = '\b';
|
||||
break;
|
||||
case event.DOM_VK_TAB:
|
||||
ch = '\t';
|
||||
// Stop tab from moving to another element.
|
||||
event.preventDefault();
|
||||
break;
|
||||
case event.DOM_VK_RETURN:
|
||||
case event.DOM_VK_ENTER:
|
||||
ch = '\n';
|
||||
break;
|
||||
case event.DOM_VK_UP:
|
||||
ch = '\x1b[A';
|
||||
break;
|
||||
case event.DOM_VK_DOWN:
|
||||
ch = '\x1b[B';
|
||||
break;
|
||||
case event.DOM_VK_RIGHT:
|
||||
ch = '\x1b[C';
|
||||
break;
|
||||
case event.DOM_VK_LEFT:
|
||||
ch = '\x1b[D';
|
||||
break;
|
||||
case event.DOM_VK_DELETE:
|
||||
ch = '\x1b[3~';
|
||||
break;
|
||||
case event.DOM_VK_HOME:
|
||||
ch = '\x1b[H';
|
||||
break;
|
||||
case event.DOM_VK_ESCAPE:
|
||||
ch = '\x1bc';
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
vt.key_buf_.push(ch);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// this is actually an event handler
|
||||
VT100.handle_onkeydown_ = function VT100_handle_onkeydown()
|
||||
{
|
||||
var vt = VT100.the_vt_, ch;
|
||||
switch (event.keyCode) {
|
||||
case 8:
|
||||
ch = '\b'; break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
vt.key_buf_.push(ch);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
VT100.go_getch_ = function VT100_go_getch()
|
||||
{
|
||||
var vt = VT100.the_vt_;
|
||||
if (vt === undefined)
|
||||
return;
|
||||
var isr = vt.getch_isr_;
|
||||
vt.getch_isr_ = undefined;
|
||||
if (isr === undefined)
|
||||
return;
|
||||
var ch = vt.key_buf_.shift();
|
||||
if (ch === undefined) {
|
||||
vt.getch_isr_ = isr;
|
||||
return;
|
||||
}
|
||||
if (vt.echo_)
|
||||
vt.addch(ch);
|
||||
isr(ch, vt);
|
||||
}
|
||||
|
||||
// object methods
|
||||
|
||||
VT100.prototype.may_scroll_ = function()
|
||||
{
|
||||
var ht = this.ht_, cr = this.row_;
|
||||
while (cr >= ht) {
|
||||
this.scroll();
|
||||
--cr;
|
||||
}
|
||||
this.row_ = cr;
|
||||
}
|
||||
|
||||
VT100.prototype.html_colours_ = function(attr)
|
||||
{
|
||||
var fg, bg, co0, co1;
|
||||
fg = attr.fg;
|
||||
bg = attr.bg;
|
||||
switch (attr.mode & (VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD)) {
|
||||
case 0:
|
||||
case VT100.A_DIM | VT100.A_BOLD:
|
||||
co0 = '00'; co1 = 'c0';
|
||||
break;
|
||||
case VT100.A_BOLD:
|
||||
co0 = '00'; co1 = 'ff';
|
||||
break;
|
||||
case VT100.A_DIM:
|
||||
if (fg == VT100.COLOR_BLACK)
|
||||
co0 = '40';
|
||||
else
|
||||
co0 = '00';
|
||||
co1 = '40';
|
||||
break;
|
||||
case VT100.A_REVERSE:
|
||||
case VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD:
|
||||
co0 = 'c0'; co1 = '40';
|
||||
break;
|
||||
case VT100.A_REVERSE | VT100.A_BOLD:
|
||||
co0 = 'c0'; co1 = '00';
|
||||
break;
|
||||
default:
|
||||
if (fg == VT100.COLOR_BLACK)
|
||||
co0 = '80';
|
||||
else
|
||||
co0 = 'c0';
|
||||
co1 = 'c0';
|
||||
}
|
||||
return {
|
||||
f: '#' + (fg & 4 ? co1 : co0) +
|
||||
(fg & 2 ? co1 : co0) +
|
||||
(fg & 1 ? co1 : co0),
|
||||
b: '#' + (bg & 4 ? co1 : co0) +
|
||||
(bg & 2 ? co1 : co0) +
|
||||
(bg & 1 ? co1 : co0)
|
||||
};
|
||||
}
|
||||
|
||||
VT100.prototype.addch = function(ch, attr)
|
||||
{
|
||||
var cc = this.col_;
|
||||
this.debug("addch:: ch: " + ch + ", attr: " + attr);
|
||||
switch (ch) {
|
||||
case '\b':
|
||||
if (cc != 0)
|
||||
--cc;
|
||||
break;
|
||||
case '\n':
|
||||
++this.row_;
|
||||
cc = 0;
|
||||
this.clrtoeol();
|
||||
this.may_scroll_();
|
||||
break;
|
||||
case '\r':
|
||||
this.may_scroll_();
|
||||
cc = 0;
|
||||
break;
|
||||
case '\t':
|
||||
this.may_scroll_();
|
||||
cc += VT100.TABSIZE - cc % VT100.TABSIZE;
|
||||
if (cc >= this.wd_) {
|
||||
++this.row_;
|
||||
cc -= this.wd_;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (attr === undefined)
|
||||
attr = this._cloneAttr(this.c_attr_);
|
||||
if (cc >= this.wd_) {
|
||||
++this.row_;
|
||||
cc = 0;
|
||||
}
|
||||
this.may_scroll_();
|
||||
this.text_[this.row_][cc] = ch;
|
||||
this.attr_[this.row_][cc] = attr;
|
||||
++cc;
|
||||
}
|
||||
this.col_ = cc;
|
||||
}
|
||||
|
||||
VT100.prototype.addstr = function(stuff)
|
||||
{
|
||||
for (var i = 0; i < stuff.length; ++i)
|
||||
this.addch(stuff.charAt(i));
|
||||
}
|
||||
|
||||
VT100.prototype._cloneAttr = function VT100_cloneAttr(a)
|
||||
{
|
||||
return {
|
||||
mode: a.mode,
|
||||
fg: a.fg,
|
||||
bg: a.bg
|
||||
};
|
||||
}
|
||||
|
||||
VT100.prototype.attroff = function(a)
|
||||
{
|
||||
//dump("attroff: " + a + "\n");
|
||||
a &= VT100.ATTR_FLAGS_;
|
||||
this.c_attr_.mode &= ~a;
|
||||
}
|
||||
|
||||
VT100.prototype.attron = function(a)
|
||||
{
|
||||
//dump("attron: " + a + "\n");
|
||||
a &= VT100.ATTR_FLAGS_;
|
||||
this.c_attr_.mode |= a;
|
||||
}
|
||||
|
||||
VT100.prototype.attrset = function(a)
|
||||
{
|
||||
//dump("attrset: " + a + "\n");
|
||||
this.c_attr_.mode = a;
|
||||
}
|
||||
|
||||
VT100.prototype.fgset = function(fg)
|
||||
{
|
||||
//dump("fgset: " + fg + "\n");
|
||||
this.c_attr_.fg = fg;
|
||||
}
|
||||
|
||||
VT100.prototype.bgset = function(bg)
|
||||
{
|
||||
//dump("bgset: " + bg + "\n");
|
||||
if (bg !== 0) {
|
||||
this.warn("bgset: " + bg + "\n");
|
||||
}
|
||||
this.c_attr_.bg = bg;
|
||||
}
|
||||
|
||||
VT100.prototype.bkgdset = function(a)
|
||||
{
|
||||
this.bkgd_ = a;
|
||||
}
|
||||
|
||||
VT100.prototype.clear = function()
|
||||
{
|
||||
this.debug("clear");
|
||||
this.row_ = this.col_ = 0;
|
||||
this.scrolled_ = 0;
|
||||
for (r = 0; r < this.ht_; ++r) {
|
||||
for (c = 0; c < this.wd_; ++c) {
|
||||
this.text_[r][c] = ' ';
|
||||
this.attr_[r][c] = this._cloneAttr(this.bkgd_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.clrtobot = function()
|
||||
{
|
||||
this.debug("clrtobot, row: " + this.row_);
|
||||
var ht = this.ht_;
|
||||
var wd = this.wd_;
|
||||
this.clrtoeol();
|
||||
for (var r = this.row_ + 1; r < ht; ++r) {
|
||||
for (var c = 0; c < wd; ++c) {
|
||||
this.text_[r][c] = ' ';
|
||||
this.attr_[r][c] = this.bkgd_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.clrtoeol = function()
|
||||
{
|
||||
this.debug("clrtoeol, col: " + this.col_);
|
||||
var r = this.row_;
|
||||
if (r >= this.ht_)
|
||||
return;
|
||||
for (var c = this.col_; c < this.wd_; ++c) {
|
||||
this.text_[r][c] = ' ';
|
||||
this.attr_[r][c] = this.bkgd_;
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.clearpos = function(row, col)
|
||||
{
|
||||
this.debug("clearpos (" + row + ", " + col + ")");
|
||||
if (row < 0 || row >= this.ht_)
|
||||
return;
|
||||
if (col < 0 || col >= this.wd_)
|
||||
return;
|
||||
this.text_[row][col] = ' ';
|
||||
this.attr_[row][col] = this.bkgd_;
|
||||
}
|
||||
|
||||
VT100.prototype.curs_set = function(vis, grab, eventist)
|
||||
{
|
||||
this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
|
||||
if (vis !== undefined)
|
||||
this.cursor_vis_ = (vis > 0);
|
||||
if (eventist === undefined)
|
||||
eventist = window;
|
||||
if (grab === true || grab === false) {
|
||||
if (grab === this.grab_events_)
|
||||
return;
|
||||
if (grab) {
|
||||
this.grab_events_ = true;
|
||||
VT100.the_vt_ = this;
|
||||
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
if (VT100.browser_ie_)
|
||||
document.onkeydown = VT100.handle_onkeydown_;
|
||||
} else {
|
||||
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
if (VT100.browser_ie_)
|
||||
document.onkeydown = VT100.handle_onkeydown_;
|
||||
this.grab_events_ = false;
|
||||
VT100.the_vt_ = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.echo = function()
|
||||
{
|
||||
this.debug("echo on");
|
||||
this.echo_ = true;
|
||||
}
|
||||
|
||||
VT100.prototype.erase = VT100.prototype.clear;
|
||||
|
||||
VT100.prototype.getch = function(isr)
|
||||
{
|
||||
this.debug("getch");
|
||||
this.refresh();
|
||||
this.getch_isr_ = isr;
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
}
|
||||
|
||||
VT100.prototype.getmaxyx = function()
|
||||
{
|
||||
return { y: this.ht_ - 1, x: this.wd_ - 1 };
|
||||
}
|
||||
|
||||
VT100.prototype.getyx = function()
|
||||
{
|
||||
return { y: this.row_, x: this.col_ };
|
||||
}
|
||||
|
||||
VT100.prototype.move = function(r, c)
|
||||
{
|
||||
this.debug("move: (" + r + ", " + c + ")");
|
||||
if (r < 0)
|
||||
r = 0;
|
||||
else if (r >= this.ht_)
|
||||
r = this.ht_ - 1;
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
else if (c >= this.wd_)
|
||||
c = this.wd_ - 1;
|
||||
this.row_ = r;
|
||||
this.col_ = c;
|
||||
}
|
||||
|
||||
VT100.prototype.noecho = function()
|
||||
{
|
||||
this.debug("echo off");
|
||||
this.echo_ = false;
|
||||
}
|
||||
|
||||
VT100.prototype.refresh = function()
|
||||
{
|
||||
this.debug("refresh");
|
||||
var r, c, stuff = "", start_tag = "", end_tag = "", at = -1, n_at, ch,
|
||||
pair, cr, cc, ht, wd, cv, added_end_tag;
|
||||
ht = this.ht_;
|
||||
wd = this.wd_;
|
||||
cr = this.row_;
|
||||
cc = this.col_;
|
||||
cv = this.cursor_vis_;
|
||||
var innerHTML = this.scr_.innerHTML;
|
||||
if (cc >= wd)
|
||||
cc = wd - 1;
|
||||
for (r = 0; r < ht; ++r) {
|
||||
if (r > 0) {
|
||||
stuff += '\n';
|
||||
}
|
||||
for (c = 0; c < wd; ++c) {
|
||||
added_end_tag = false;
|
||||
n_at = this.attr_[r][c];
|
||||
if (cv && r == cr && c == cc) {
|
||||
// Draw the cursor here.
|
||||
n_at = this._cloneAttr(n_at);
|
||||
n_at.mode ^= VT100.A_REVERSE;
|
||||
}
|
||||
// If the attributes changed, make a new span.
|
||||
if (n_at.mode != at.mode || n_at.fg != at.fg || n_at.bg != at.bg) {
|
||||
if (c > 0) {
|
||||
stuff += end_tag;
|
||||
}
|
||||
start_tag = "";
|
||||
end_tag = "";
|
||||
if (n_at.mode & VT100.A_BLINK) {
|
||||
start_tag = "<blink>";
|
||||
end_tag = "</blink>" + end_tag;
|
||||
}
|
||||
if (n_at.mode & VT100.A_STANDOUT)
|
||||
n_at.mode |= VT100.A_BOLD;
|
||||
pair = this.html_colours_(n_at);
|
||||
start_tag += '<span style="color:' + pair.f +
|
||||
';background-color:' + pair.b;
|
||||
if (n_at.mode & VT100.A_UNDERLINE)
|
||||
start_tag += ';text-decoration:underline';
|
||||
start_tag += ';">';
|
||||
stuff += start_tag;
|
||||
end_tag = "</span>" + end_tag;
|
||||
at = n_at;
|
||||
added_end_tag = true;
|
||||
} else if (c == 0) {
|
||||
stuff += start_tag;
|
||||
}
|
||||
ch = this.text_[r][c];
|
||||
switch (ch) {
|
||||
case '&':
|
||||
stuff += '&'; break;
|
||||
case '<':
|
||||
stuff += '<'; break;
|
||||
case '>':
|
||||
stuff += '>'; break;
|
||||
case ' ':
|
||||
//stuff += ' '; break;
|
||||
stuff += ' '; break;
|
||||
default:
|
||||
stuff += ch;
|
||||
}
|
||||
}
|
||||
if (!added_end_tag)
|
||||
stuff += end_tag;
|
||||
}
|
||||
this.scr_.innerHTML = "<b>" + stuff + "</b>\n";
|
||||
}
|
||||
|
||||
VT100.prototype.scroll = function()
|
||||
{
|
||||
this.scrolled_ += 1;
|
||||
this.debug("scrolled: " + this.scrolled_);
|
||||
var n_text = this.text_[0], n_attr = this.attr_[0],
|
||||
ht = this.ht_, wd = this.wd_;
|
||||
for (var r = 1; r < ht; ++r) {
|
||||
this.text_[r - 1] = this.text_[r];
|
||||
this.attr_[r - 1] = this.attr_[r];
|
||||
}
|
||||
this.text_[ht - 1] = n_text;
|
||||
this.attr_[ht - 1] = n_attr;
|
||||
for (var c = 0; c < wd; ++c) {
|
||||
n_text[c] = ' ';
|
||||
n_attr[c] = this.bkgd_;
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.standend = function()
|
||||
{
|
||||
//this.debug("standend");
|
||||
this.attrset(0);
|
||||
}
|
||||
|
||||
VT100.prototype.standout = function()
|
||||
{
|
||||
//this.debug("standout");
|
||||
this.attron(VT100.A_STANDOUT);
|
||||
}
|
||||
|
||||
VT100.prototype.write = function(stuff)
|
||||
{
|
||||
var ch, x, r, c, i, j, yx, myx;
|
||||
for (i = 0; i < stuff.length; ++i) {
|
||||
ch = stuff.charAt(i);
|
||||
if (ch == '\x0D') {
|
||||
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '\\x0D'");
|
||||
} else {
|
||||
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'");
|
||||
}
|
||||
//dump("ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'\n");
|
||||
switch (ch) {
|
||||
case '\x00':
|
||||
case '\x7f':
|
||||
case '\x07': /* bell, ignore it */
|
||||
this.debug("write:: ignoring bell character: " + ch);
|
||||
continue;
|
||||
case '\a':
|
||||
case '\b':
|
||||
case '\t':
|
||||
case '\r':
|
||||
this.addch(ch);
|
||||
continue;
|
||||
case '\n':
|
||||
case '\v':
|
||||
case '\f': // what a mess
|
||||
yx = this.getyx();
|
||||
myx = this.getmaxyx();
|
||||
if (yx.y >= myx.y) {
|
||||
this.scroll();
|
||||
this.move(myx.y, 0);
|
||||
} else
|
||||
this.move(yx.y + 1, 0);
|
||||
continue;
|
||||
case '\x18':
|
||||
case '\x1a':
|
||||
this.esc_state_ = 0;
|
||||
this.debug("write:: set escape state: 0");
|
||||
continue;
|
||||
case '\x1b':
|
||||
this.esc_state_ = 1;
|
||||
this.debug("write:: set escape state: 1");
|
||||
continue;
|
||||
case '\x9b':
|
||||
this.esc_state_ = 2;
|
||||
this.debug("write:: set escape state: 2");
|
||||
continue;
|
||||
}
|
||||
// not a recognized control character
|
||||
switch (this.esc_state_) {
|
||||
case 0: // not in escape sequence
|
||||
this.addch(ch);
|
||||
break;
|
||||
case 1: // just saw ESC
|
||||
switch (ch) {
|
||||
case '[':
|
||||
this.esc_state_ = 2;
|
||||
this.debug("write:: set escape state: 2");
|
||||
break;
|
||||
case '=':
|
||||
/* Set keypade mode (ignored) */
|
||||
this.debug("write:: set keypade mode: ignored");
|
||||
this.esc_state_ = 0;
|
||||
break;
|
||||
case '>':
|
||||
/* Reset keypade mode (ignored) */
|
||||
this.debug("write:: reset keypade mode: ignored");
|
||||
this.esc_state_ = 0;
|
||||
break;
|
||||
case 'H':
|
||||
/* Set tab at cursor column (ignored) */
|
||||
this.debug("write:: set tab cursor column: ignored");
|
||||
this.esc_state_ = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2: // just saw CSI
|
||||
switch (ch) {
|
||||
case 'K':
|
||||
/* Erase in Line */
|
||||
this.esc_state_ = 0;
|
||||
this.clrtoeol();
|
||||
continue;
|
||||
case 'H':
|
||||
/* Move to (0,0). */
|
||||
this.esc_state_ = 0;
|
||||
this.move(0, 0);
|
||||
continue;
|
||||
case 'J':
|
||||
/* Clear to the bottom. */
|
||||
this.esc_state_ = 0;
|
||||
this.clrtobot();
|
||||
continue;
|
||||
case '?':
|
||||
/* Special VT100 mode handling. */
|
||||
this.esc_state_ = 5;
|
||||
this.debug("write:: special vt100 mode");
|
||||
continue;
|
||||
}
|
||||
// Drop through to next case.
|
||||
this.csi_parms_ = [0];
|
||||
this.debug("write:: set escape state: 3");
|
||||
this.esc_state_ = 3;
|
||||
case 3: // saw CSI and parameters
|
||||
switch (ch) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
x = this.csi_parms_.pop();
|
||||
this.csi_parms_.push(x * 10 + ch * 1);
|
||||
this.debug("csi_parms_: " + this.csi_parms_);
|
||||
continue;
|
||||
case ';':
|
||||
if (this.csi_parms_.length < 17)
|
||||
this.csi_parms_.push(0);
|
||||
continue;
|
||||
}
|
||||
this.esc_state_ = 0;
|
||||
switch (ch) {
|
||||
case 'A':
|
||||
// Cursor Up <ESC>[{COUNT}A
|
||||
this.move(this.row_ - Math.max(1, this.csi_parms_[0]),
|
||||
this.col_);
|
||||
break;
|
||||
case 'B':
|
||||
// Cursor Down <ESC>[{COUNT}B
|
||||
this.move(this.row_ + Math.max(1, this.csi_parms_[0]),
|
||||
this.col_);
|
||||
break;
|
||||
case 'C':
|
||||
// Cursor Forward <ESC>[{COUNT}C
|
||||
this.move(this.row_,
|
||||
this.col_ + Math.max(1, this.csi_parms_[0]));
|
||||
break;
|
||||
case 'c':
|
||||
this.warn("write:: got TERM query");
|
||||
break;
|
||||
case 'D':
|
||||
// Cursor Backward <ESC>[{COUNT}D
|
||||
this.move(this.row_,
|
||||
this.col_ - Math.max(1, this.csi_parms_[0]));
|
||||
break;
|
||||
case 'f':
|
||||
case 'H':
|
||||
// Cursor Home <ESC>[{ROW};{COLUMN}H
|
||||
this.csi_parms_.push(0);
|
||||
this.move(this.csi_parms_[0] - 1,
|
||||
this.csi_parms_[1] - 1);
|
||||
break;
|
||||
case 'J':
|
||||
switch (this.csi_parms_[0]) {
|
||||
case 0:
|
||||
this.clrtobot();
|
||||
break;
|
||||
case 2:
|
||||
this.clear();
|
||||
this.move(0, 0);
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
for (j=0; j<this.csi_parms_.length; ++j) {
|
||||
x = this.csi_parms_[j];
|
||||
switch (x) {
|
||||
case 0:
|
||||
this.standend();
|
||||
this.fgset(this.bkgd_.fg);
|
||||
this.bgset(this.bkgd_.bg);
|
||||
break;
|
||||
case 1:
|
||||
this.attron(VT100.A_BOLD);
|
||||
break;
|
||||
case 30:
|
||||
this.fgset(VT100.COLOR_BLACK);
|
||||
break;
|
||||
case 31:
|
||||
this.fgset(VT100.COLOR_RED);
|
||||
break;
|
||||
case 32:
|
||||
this.fgset(VT100.COLOR_GREEN);
|
||||
break;
|
||||
case 33:
|
||||
this.fgset(VT100.COLOR_YELLOW);
|
||||
break;
|
||||
case 34:
|
||||
this.fgset(VT100.COLOR_BLUE);
|
||||
break;
|
||||
case 35:
|
||||
this.fgset(VT100.COLOR_MAGENTA);
|
||||
break;
|
||||
case 36:
|
||||
this.fgset(VT100.COLOR_CYAN);
|
||||
break;
|
||||
case 37:
|
||||
this.fgset(VT100.COLOR_WHITE);
|
||||
break;
|
||||
case 40:
|
||||
this.bgset(VT100.COLOR_BLACK);
|
||||
break;
|
||||
case 41:
|
||||
this.bgset(VT100.COLOR_RED);
|
||||
break;
|
||||
case 42:
|
||||
this.bgset(VT100.COLOR_GREEN);
|
||||
break;
|
||||
case 44:
|
||||
this.bgset(VT100.COLOR_YELLOW);
|
||||
break;
|
||||
case 44:
|
||||
this.bgset(VT100.COLOR_BLUE);
|
||||
break;
|
||||
case 45:
|
||||
this.bgset(VT100.COLOR_MAGENTA);
|
||||
break;
|
||||
case 46:
|
||||
this.bgset(VT100.COLOR_CYAN);
|
||||
break;
|
||||
case 47:
|
||||
this.bgset(VT100.COLOR_WHITE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
// 1,24r - set scrolling region (ignored)
|
||||
break;
|
||||
case '[':
|
||||
this.debug("write:: set escape state: 4");
|
||||
this.esc_state_ = 4;
|
||||
break;
|
||||
case 'g':
|
||||
// 0g: clear tab at cursor (ignored)
|
||||
// 3g: clear all tabs (ignored)
|
||||
break;
|
||||
default:
|
||||
this.warn("write:: unknown command: " + ch);
|
||||
this.csi_parms_ = [];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4: // saw CSI [
|
||||
this.esc_state_ = 0; // gobble char.
|
||||
break;
|
||||
case 5: // Special mode handling, saw <ESC>[?
|
||||
// Expect a number - the reset type
|
||||
this.csi_parms_ = [ch];
|
||||
this.esc_state_ = 6;
|
||||
break;
|
||||
case 6: // Reset mode handling, saw <ESC>[?1
|
||||
// Expect a letter - the mode target, example:
|
||||
// <ESC>[?1l : cursor key mode = cursor
|
||||
// <ESC>[?1h : save current screen, create new empty
|
||||
// screen and position at 0,0
|
||||
// <ESC>[?5l : White on blk
|
||||
// XXX: Ignored for now.
|
||||
//dump("Saw reset mode: <ESC>[?" + this.csi_parms_[0] + ch + "\n");
|
||||
this.esc_state_ = 0;
|
||||
this.debug("write:: set escape state: 0");
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
VT100.prototype.debug = function(message) {
|
||||
if (this.debug_) {
|
||||
dump(message + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
VT100.prototype.warn = function(message) {
|
||||
dump(message + "\n");
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
|
||||
|
||||
/*jslint white: false, bitwise: false, plusplus: false */
|
||||
/*global console */
|
||||
|
||||
var Base64 = {
|
||||
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
|
||||
base64Pad : '=',
|
||||
|
||||
encode: function (data) {
|
||||
"use strict";
|
||||
var result = '';
|
||||
var toBase64Table = Base64.toBase64Table;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
var length = data.length;
|
||||
var i;
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
/* BEGIN LOOP */
|
||||
for (i = 0; i < (length - 2); i += 3) {
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||
result += toBase64Table[data[i+2] & 0x3f];
|
||||
}
|
||||
/* END LOOP */
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
if (length%3) {
|
||||
i = length - (length%3);
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
if ((length%3) === 2) {
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[(data[i+1] & 0x0f) << 2];
|
||||
result += base64Pad;
|
||||
} else {
|
||||
result += toBase64Table[(data[i] & 0x03) << 4];
|
||||
result += base64Pad + base64Pad;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Convert Base64 data to a string */
|
||||
toBinaryTable : [
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
|
||||
-1, 0, 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,-1, -1,-1,-1,-1,
|
||||
-1,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,-1, -1,-1,-1,-1
|
||||
],
|
||||
|
||||
decode: function (data, offset) {
|
||||
"use strict";
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
var toBinaryTable = Base64.toBinaryTable;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
var result, result_length, idx, i, c, padding;
|
||||
var leftbits = 0; // number of bits decoded, but yet to be appended
|
||||
var leftdata = 0; // bits decoded, but yet to be appended
|
||||
var data_length = data.indexOf('=') - offset;
|
||||
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
|
||||
result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
/* BEGIN LOOP */
|
||||
for (idx = 0, i = offset; i < data.length; i++) {
|
||||
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
|
||||
padding = (data.charAt(i) === base64Pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect data into leftdata, update bitcount
|
||||
leftdata = (leftdata << 6) | c;
|
||||
leftbits += 6;
|
||||
|
||||
// If we have 8 or more bits, append 8 bits to the result
|
||||
if (leftbits >= 8) {
|
||||
leftbits -= 8;
|
||||
// Append if not padding.
|
||||
if (!padding) {
|
||||
result[idx++] = (leftdata >> leftbits) & 0xff;
|
||||
}
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
}
|
||||
/* END LOOP */
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
throw {name: 'Base64-Error',
|
||||
message: 'Corrupted base64 string'};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}; /* End of Base64 namespace */
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* from noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2010 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
/* Translate DOM key down/up event to keysym value */
|
||||
function getKeysym(e) {
|
||||
var evt, keysym;
|
||||
evt = (e ? e : window.event);
|
||||
|
||||
/* Remap modifier and special keys */
|
||||
switch ( evt.keyCode ) {
|
||||
case 8 : keysym = 0xFF08; break; // BACKSPACE
|
||||
case 9 : keysym = 0xFF09; break; // TAB
|
||||
case 13 : keysym = 0xFF0D; break; // ENTER
|
||||
case 27 : keysym = 0xFF1B; break; // ESCAPE
|
||||
case 45 : keysym = 0xFF63; break; // INSERT
|
||||
case 46 : keysym = 0xFFFF; break; // DELETE
|
||||
case 36 : keysym = 0xFF50; break; // HOME
|
||||
case 35 : keysym = 0xFF57; break; // END
|
||||
case 33 : keysym = 0xFF55; break; // PAGE_UP
|
||||
case 34 : keysym = 0xFF56; break; // PAGE_DOWN
|
||||
case 37 : keysym = 0xFF51; break; // LEFT
|
||||
case 38 : keysym = 0xFF52; break; // UP
|
||||
case 39 : keysym = 0xFF53; break; // RIGHT
|
||||
case 40 : keysym = 0xFF54; break; // DOWN
|
||||
case 112 : keysym = 0xFFBE; break; // F1
|
||||
case 113 : keysym = 0xFFBF; break; // F2
|
||||
case 114 : keysym = 0xFFC0; break; // F3
|
||||
case 115 : keysym = 0xFFC1; break; // F4
|
||||
case 116 : keysym = 0xFFC2; break; // F5
|
||||
case 117 : keysym = 0xFFC3; break; // F6
|
||||
case 118 : keysym = 0xFFC4; break; // F7
|
||||
case 119 : keysym = 0xFFC5; break; // F8
|
||||
case 120 : keysym = 0xFFC6; break; // F9
|
||||
case 121 : keysym = 0xFFC7; break; // F10
|
||||
case 122 : keysym = 0xFFC8; break; // F11
|
||||
case 123 : keysym = 0xFFC9; break; // F12
|
||||
case 16 : keysym = 0xFFE1; break; // SHIFT
|
||||
case 17 : keysym = 0xFFE3; break; // CONTROL
|
||||
//case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
|
||||
case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
|
||||
default : keysym = evt.keyCode; break;
|
||||
}
|
||||
|
||||
/* Remap symbols */
|
||||
switch (keysym) {
|
||||
case 186 : keysym = 59; break; // ; (IE)
|
||||
case 187 : keysym = 61; break; // = (IE)
|
||||
case 188 : keysym = 44; break; // , (Mozilla, IE)
|
||||
case 109 : // - (Mozilla)
|
||||
if (Util.Engine.gecko) {
|
||||
keysym = 45; }
|
||||
break;
|
||||
case 189 : keysym = 45; break; // - (IE)
|
||||
case 190 : keysym = 46; break; // . (Mozilla, IE)
|
||||
case 191 : keysym = 47; break; // / (Mozilla, IE)
|
||||
case 192 : keysym = 96; break; // ` (Mozilla, IE)
|
||||
case 219 : keysym = 91; break; // [ (Mozilla, IE)
|
||||
case 220 : keysym = 92; break; // \ (Mozilla, IE)
|
||||
case 221 : keysym = 93; break; // ] (Mozilla, IE)
|
||||
case 222 : keysym = 39; break; // ' (Mozilla, IE)
|
||||
}
|
||||
|
||||
/* Remap shifted and unshifted keys */
|
||||
if (!!evt.shiftKey) {
|
||||
switch (keysym) {
|
||||
case 48 : keysym = 41 ; break; // ) (shifted 0)
|
||||
case 49 : keysym = 33 ; break; // ! (shifted 1)
|
||||
case 50 : keysym = 64 ; break; // @ (shifted 2)
|
||||
case 51 : keysym = 35 ; break; // # (shifted 3)
|
||||
case 52 : keysym = 36 ; break; // $ (shifted 4)
|
||||
case 53 : keysym = 37 ; break; // % (shifted 5)
|
||||
case 54 : keysym = 94 ; break; // ^ (shifted 6)
|
||||
case 55 : keysym = 38 ; break; // & (shifted 7)
|
||||
case 56 : keysym = 42 ; break; // * (shifted 8)
|
||||
case 57 : keysym = 40 ; break; // ( (shifted 9)
|
||||
|
||||
case 59 : keysym = 58 ; break; // : (shifted `)
|
||||
case 61 : keysym = 43 ; break; // + (shifted ;)
|
||||
case 44 : keysym = 60 ; break; // < (shifted ,)
|
||||
case 45 : keysym = 95 ; break; // _ (shifted -)
|
||||
case 46 : keysym = 62 ; break; // > (shifted .)
|
||||
case 47 : keysym = 63 ; break; // ? (shifted /)
|
||||
case 96 : keysym = 126; break; // ~ (shifted `)
|
||||
case 91 : keysym = 123; break; // { (shifted [)
|
||||
case 92 : keysym = 124; break; // | (shifted \)
|
||||
case 93 : keysym = 125; break; // } (shifted ])
|
||||
case 39 : keysym = 34 ; break; // " (shifted ')
|
||||
}
|
||||
} else if ((keysym >= 65) && (keysym <=90)) {
|
||||
/* Remap unshifted A-Z */
|
||||
keysym += 32;
|
||||
}
|
||||
|
||||
return keysym;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* from noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
/*jslint bitwise: false, white: false */
|
||||
/*global window, console, document, navigator, ActiveXObject */
|
||||
|
||||
// Globals defined here
|
||||
var Util = {};
|
||||
|
||||
|
||||
/*
|
||||
* Make arrays quack
|
||||
*/
|
||||
|
||||
Array.prototype.push8 = function (num) {
|
||||
this.push(num & 0xFF);
|
||||
};
|
||||
|
||||
Array.prototype.push16 = function (num) {
|
||||
this.push((num >> 8) & 0xFF,
|
||||
(num ) & 0xFF );
|
||||
};
|
||||
Array.prototype.push32 = function (num) {
|
||||
this.push((num >> 24) & 0xFF,
|
||||
(num >> 16) & 0xFF,
|
||||
(num >> 8) & 0xFF,
|
||||
(num ) & 0xFF );
|
||||
};
|
||||
|
||||
// IE does not support map (even in IE9)
|
||||
//This prototype is provided by the Mozilla foundation and
|
||||
//is distributed under the MIT license.
|
||||
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
|
||||
if (!Array.prototype.map)
|
||||
{
|
||||
Array.prototype.map = function(fun /*, thisp*/)
|
||||
{
|
||||
var len = this.length;
|
||||
if (typeof fun != "function")
|
||||
throw new TypeError();
|
||||
|
||||
var res = new Array(len);
|
||||
var thisp = arguments[1];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (i in this)
|
||||
res[i] = fun.call(thisp, this[i], i, this);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// requestAnimationFrame shim with setTimeout fallback
|
||||
//
|
||||
|
||||
window.requestAnimFrame = (function(){
|
||||
return window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback){
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
})();
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in Util
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Logging/debug routines
|
||||
*/
|
||||
|
||||
Util._log_level = 'warn';
|
||||
Util.init_logging = function (level) {
|
||||
if (typeof level === 'undefined') {
|
||||
level = Util._log_level;
|
||||
} else {
|
||||
Util._log_level = level;
|
||||
}
|
||||
if (typeof window.console === "undefined") {
|
||||
if (typeof window.opera !== "undefined") {
|
||||
window.console = {
|
||||
'log' : window.opera.postError,
|
||||
'warn' : window.opera.postError,
|
||||
'error': window.opera.postError };
|
||||
} else {
|
||||
window.console = {
|
||||
'log' : function(m) {},
|
||||
'warn' : function(m) {},
|
||||
'error': function(m) {}};
|
||||
}
|
||||
}
|
||||
|
||||
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
|
||||
switch (level) {
|
||||
case 'debug': Util.Debug = function (msg) { console.log(msg); };
|
||||
case 'info': Util.Info = function (msg) { console.log(msg); };
|
||||
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
|
||||
case 'error': Util.Error = function (msg) { console.error(msg); };
|
||||
case 'none':
|
||||
break;
|
||||
default:
|
||||
throw("invalid logging type '" + level + "'");
|
||||
}
|
||||
};
|
||||
Util.get_logging = function () {
|
||||
return Util._log_level;
|
||||
};
|
||||
// Initialize logging level
|
||||
Util.init_logging();
|
||||
|
||||
|
||||
// Set configuration default for Crockford style function namespaces
|
||||
Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
|
||||
var getter, setter;
|
||||
|
||||
// Default getter function
|
||||
getter = function (idx) {
|
||||
if ((type in {'arr':1, 'array':1}) &&
|
||||
(typeof idx !== 'undefined')) {
|
||||
return cfg[v][idx];
|
||||
} else {
|
||||
return cfg[v];
|
||||
}
|
||||
};
|
||||
|
||||
// Default setter function
|
||||
setter = function (val, idx) {
|
||||
if (type in {'boolean':1, 'bool':1}) {
|
||||
if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
|
||||
val = false;
|
||||
} else {
|
||||
val = true;
|
||||
}
|
||||
} else if (type in {'integer':1, 'int':1}) {
|
||||
val = parseInt(val, 10);
|
||||
} else if (type === 'str') {
|
||||
val = String(val);
|
||||
} else if (type === 'func') {
|
||||
if (!val) {
|
||||
val = function () {};
|
||||
}
|
||||
}
|
||||
if (typeof idx !== 'undefined') {
|
||||
cfg[v][idx] = val;
|
||||
} else {
|
||||
cfg[v] = val;
|
||||
}
|
||||
};
|
||||
|
||||
// Set the description
|
||||
api[v + '_description'] = desc;
|
||||
|
||||
// Set the getter function
|
||||
if (typeof api['get_' + v] === 'undefined') {
|
||||
api['get_' + v] = getter;
|
||||
}
|
||||
|
||||
// Set the setter function with extra sanity checks
|
||||
if (typeof api['set_' + v] === 'undefined') {
|
||||
api['set_' + v] = function (val, idx) {
|
||||
if (mode in {'RO':1, 'ro':1}) {
|
||||
throw(v + " is read-only");
|
||||
} else if ((mode in {'WO':1, 'wo':1}) &&
|
||||
(typeof cfg[v] !== 'undefined')) {
|
||||
throw(v + " can only be set once");
|
||||
}
|
||||
setter(val, idx);
|
||||
};
|
||||
}
|
||||
|
||||
// Set the default value
|
||||
if (typeof defaults[v] !== 'undefined') {
|
||||
defval = defaults[v];
|
||||
} else if ((type in {'arr':1, 'array':1}) &&
|
||||
(! (defval instanceof Array))) {
|
||||
defval = [];
|
||||
}
|
||||
// Coerce existing setting to the right type
|
||||
//Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
|
||||
setter(defval);
|
||||
};
|
||||
|
||||
// Set group of configuration defaults
|
||||
Util.conf_defaults = function(cfg, api, defaults, arr) {
|
||||
var i;
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
|
||||
arr[i][2], arr[i][3], arr[i][4]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Cross-browser routines
|
||||
*/
|
||||
|
||||
|
||||
// Dynamically load scripts without using document.write()
|
||||
// Reference: http://unixpapa.com/js/dyna.html
|
||||
//
|
||||
// Handles the case where load_scripts is invoked from a script that
|
||||
// itself is loaded via load_scripts. Once all scripts are loaded the
|
||||
// window.onscriptsloaded handler is called (if set).
|
||||
Util.get_include_uri = function() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
|
||||
}
|
||||
Util._loading_scripts = [];
|
||||
Util._pending_scripts = [];
|
||||
Util.load_scripts = function(files) {
|
||||
var head = document.getElementsByTagName('head')[0], script,
|
||||
ls = Util._loading_scripts, ps = Util._pending_scripts;
|
||||
for (var f=0; f<files.length; f++) {
|
||||
script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = Util.get_include_uri() + files[f];
|
||||
//console.log("loading script: " + script.src);
|
||||
script.onload = script.onreadystatechange = function (e) {
|
||||
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
|
||||
ls[0].readyState === 'complete')) {
|
||||
// For IE, append the script to trigger execution
|
||||
var s = ls.shift();
|
||||
//console.log("loaded script: " + s.src);
|
||||
head.appendChild(s);
|
||||
}
|
||||
if (!this.readyState ||
|
||||
(Util.Engine.presto && this.readyState === 'loaded') ||
|
||||
this.readyState === 'complete') {
|
||||
if (ps.indexOf(this) >= 0) {
|
||||
this.onload = this.onreadystatechange = null;
|
||||
//console.log("completed script: " + this.src);
|
||||
ps.splice(ps.indexOf(this), 1);
|
||||
|
||||
// Call window.onscriptsload after last script loads
|
||||
if (ps.length === 0 && window.onscriptsload) {
|
||||
window.onscriptsload();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// In-order script execution tricks
|
||||
if (Util.Engine.trident) {
|
||||
// For IE wait until readyState is 'loaded' before
|
||||
// appending it which will trigger execution
|
||||
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
||||
ls.push(script);
|
||||
} else {
|
||||
// For webkit and firefox set async=false and append now
|
||||
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
|
||||
script.async = false;
|
||||
head.appendChild(script);
|
||||
}
|
||||
ps.push(script);
|
||||
}
|
||||
}
|
||||
|
||||
// Get DOM element position on page
|
||||
Util.getPosition = function (obj) {
|
||||
var x = 0, y = 0;
|
||||
if (obj.offsetParent) {
|
||||
do {
|
||||
x += obj.offsetLeft;
|
||||
y += obj.offsetTop;
|
||||
obj = obj.offsetParent;
|
||||
} while (obj);
|
||||
}
|
||||
return {'x': x, 'y': y};
|
||||
};
|
||||
|
||||
// Get mouse event position in DOM element
|
||||
Util.getEventPosition = function (e, obj, scale) {
|
||||
var evt, docX, docY, pos;
|
||||
//if (!e) evt = window.event;
|
||||
evt = (e ? e : window.event);
|
||||
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
|
||||
if (evt.pageX || evt.pageY) {
|
||||
docX = evt.pageX;
|
||||
docY = evt.pageY;
|
||||
} else if (evt.clientX || evt.clientY) {
|
||||
docX = evt.clientX + document.body.scrollLeft +
|
||||
document.documentElement.scrollLeft;
|
||||
docY = evt.clientY + document.body.scrollTop +
|
||||
document.documentElement.scrollTop;
|
||||
}
|
||||
pos = Util.getPosition(obj);
|
||||
if (typeof scale === "undefined") {
|
||||
scale = 1;
|
||||
}
|
||||
return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
|
||||
};
|
||||
|
||||
|
||||
// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
|
||||
Util.addEvent = function (obj, evType, fn){
|
||||
if (obj.attachEvent){
|
||||
var r = obj.attachEvent("on"+evType, fn);
|
||||
return r;
|
||||
} else if (obj.addEventListener){
|
||||
obj.addEventListener(evType, fn, false);
|
||||
return true;
|
||||
} else {
|
||||
throw("Handler could not be attached");
|
||||
}
|
||||
};
|
||||
|
||||
Util.removeEvent = function(obj, evType, fn){
|
||||
if (obj.detachEvent){
|
||||
var r = obj.detachEvent("on"+evType, fn);
|
||||
return r;
|
||||
} else if (obj.removeEventListener){
|
||||
obj.removeEventListener(evType, fn, false);
|
||||
return true;
|
||||
} else {
|
||||
throw("Handler could not be removed");
|
||||
}
|
||||
};
|
||||
|
||||
Util.stopEvent = function(e) {
|
||||
if (e.stopPropagation) { e.stopPropagation(); }
|
||||
else { e.cancelBubble = true; }
|
||||
|
||||
if (e.preventDefault) { e.preventDefault(); }
|
||||
else { e.returnValue = false; }
|
||||
};
|
||||
|
||||
|
||||
// Set browser engine versions. Based on mootools.
|
||||
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
|
||||
|
||||
Util.Engine = {
|
||||
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
|
||||
//'presto': (function() {
|
||||
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
'presto': (function() { return (!window.opera) ? false : true; }()),
|
||||
|
||||
'trident': (function() {
|
||||
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
|
||||
'webkit': (function() {
|
||||
try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
|
||||
//'webkit': (function() {
|
||||
// return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
|
||||
'gecko': (function() {
|
||||
return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
|
||||
};
|
||||
if (Util.Engine.webkit) {
|
||||
// Extract actual webkit version if available
|
||||
Util.Engine.webkit = (function(v) {
|
||||
var re = new RegExp('WebKit/([0-9\.]*) ');
|
||||
v = (navigator.userAgent.match(re) || ['', v])[1];
|
||||
return parseFloat(v, 10);
|
||||
})(Util.Engine.webkit);
|
||||
}
|
||||
|
||||
Util.Flash = (function(){
|
||||
var v, version;
|
||||
try {
|
||||
v = navigator.plugins['Shockwave Flash'].description;
|
||||
} catch(err1) {
|
||||
try {
|
||||
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
|
||||
} catch(err2) {
|
||||
v = '0 r0';
|
||||
}
|
||||
}
|
||||
version = v.match(/\d+/g);
|
||||
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
|
||||
}());
|
||||
@@ -0,0 +1,109 @@
|
||||
* How to try
|
||||
|
||||
Assuming you have Web server (e.g. Apache) running at http://example.com/ .
|
||||
|
||||
- Download web_socket.rb from:
|
||||
http://github.com/gimite/web-socket-ruby/tree/master
|
||||
- Run sample Web Socket server (echo server) in example.com with: (#1)
|
||||
$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
|
||||
- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
|
||||
- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
|
||||
- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
|
||||
- Open sample.html in your browser.
|
||||
- After "onopen" is shown, input something, click [Send] and confirm echo back.
|
||||
|
||||
#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
|
||||
|
||||
|
||||
* Troubleshooting
|
||||
|
||||
If it doesn't work, try these:
|
||||
|
||||
1. Try Chrome and Firefox 3.x.
|
||||
- It doesn't work on Chrome:
|
||||
-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
|
||||
- It works on Chrome but it doesn't work on Firefox:
|
||||
-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
|
||||
- It works on both Chrome and Firefox, but it doesn't work on your browser:
|
||||
-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
|
||||
|
||||
2. Add this line before your code:
|
||||
WEB_SOCKET_DEBUG = true;
|
||||
and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
|
||||
|
||||
3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
|
||||
|
||||
4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
|
||||
|
||||
5. Check if sample.html bundled with web-socket-js works.
|
||||
|
||||
6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
|
||||
|
||||
7. Install debugger version of Flash Player available here to see Flash errors:
|
||||
http://www.adobe.com/support/flashplayer/downloads.html
|
||||
|
||||
|
||||
* Supported environments
|
||||
|
||||
It should work on:
|
||||
- Google Chrome 4 or later (just uses native implementation)
|
||||
- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
|
||||
|
||||
It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
|
||||
|
||||
|
||||
* Flash socket policy file
|
||||
|
||||
This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
|
||||
|
||||
If you use web-socket-ruby available at
|
||||
http://github.com/gimite/web-socket-ruby/tree/master
|
||||
, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
|
||||
|
||||
If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
|
||||
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
|
||||
for details and sample script to run socket policy file server. node.js implementation is available here:
|
||||
http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
|
||||
|
||||
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
|
||||
|
||||
|
||||
* Cookie considerations
|
||||
|
||||
Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
|
||||
|
||||
Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
|
||||
|
||||
|
||||
* Proxy considerations
|
||||
|
||||
The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
|
||||
|
||||
The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
|
||||
|
||||
The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
|
||||
|
||||
|
||||
* How to host HTML file and SWF file in different domains
|
||||
|
||||
By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
|
||||
|
||||
WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
|
||||
|
||||
- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
|
||||
- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
|
||||
- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
|
||||
|
||||
|
||||
* How to build WebSocketMain.swf
|
||||
|
||||
Install Flex 4 SDK:
|
||||
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
|
||||
|
||||
$ cd flash-src
|
||||
$ ./build.sh
|
||||
|
||||
|
||||
* License
|
||||
|
||||
New BSD License.
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
+391
@@ -0,0 +1,391 @@
|
||||
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
||||
// License: New BSD License
|
||||
// Reference: http://dev.w3.org/html5/websockets/
|
||||
// Reference: http://tools.ietf.org/html/rfc6455
|
||||
|
||||
(function() {
|
||||
|
||||
if (window.WEB_SOCKET_FORCE_FLASH) {
|
||||
// Keeps going.
|
||||
} else if (window.WebSocket) {
|
||||
return;
|
||||
} else if (window.MozWebSocket) {
|
||||
// Firefox.
|
||||
window.WebSocket = MozWebSocket;
|
||||
return;
|
||||
}
|
||||
|
||||
var logger;
|
||||
if (window.WEB_SOCKET_LOGGER) {
|
||||
logger = WEB_SOCKET_LOGGER;
|
||||
} else if (window.console && window.console.log && window.console.error) {
|
||||
// In some environment, console is defined but console.log or console.error is missing.
|
||||
logger = window.console;
|
||||
} else {
|
||||
logger = {log: function(){ }, error: function(){ }};
|
||||
}
|
||||
|
||||
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
|
||||
if (swfobject.getFlashPlayerVersion().major < 10) {
|
||||
logger.error("Flash Player >= 10.0.0 is required.");
|
||||
return;
|
||||
}
|
||||
if (location.protocol == "file:") {
|
||||
logger.error(
|
||||
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
||||
"unless you set Flash Security Settings properly. " +
|
||||
"Open the page via Web server i.e. http://...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Our own implementation of WebSocket class using Flash.
|
||||
* @param {string} url
|
||||
* @param {array or string} protocols
|
||||
* @param {string} proxyHost
|
||||
* @param {int} proxyPort
|
||||
* @param {string} headers
|
||||
*/
|
||||
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
|
||||
var self = this;
|
||||
self.__id = WebSocket.__nextId++;
|
||||
WebSocket.__instances[self.__id] = self;
|
||||
self.readyState = WebSocket.CONNECTING;
|
||||
self.bufferedAmount = 0;
|
||||
self.__events = {};
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (typeof protocols == "string") {
|
||||
protocols = [protocols];
|
||||
}
|
||||
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
||||
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
||||
self.__createTask = setTimeout(function() {
|
||||
WebSocket.__addTask(function() {
|
||||
self.__createTask = null;
|
||||
WebSocket.__flash.create(
|
||||
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send data to the web socket.
|
||||
* @param {string} data The data to send to the socket.
|
||||
* @return {boolean} True for success, false for failure.
|
||||
*/
|
||||
WebSocket.prototype.send = function(data) {
|
||||
if (this.readyState == WebSocket.CONNECTING) {
|
||||
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
|
||||
}
|
||||
// We use encodeURIComponent() here, because FABridge doesn't work if
|
||||
// the argument includes some characters. We don't use escape() here
|
||||
// because of this:
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
|
||||
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
|
||||
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
|
||||
// Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
|
||||
// additional testing.
|
||||
var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
|
||||
if (result < 0) { // success
|
||||
return true;
|
||||
} else {
|
||||
this.bufferedAmount += result;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close this web socket gracefully.
|
||||
*/
|
||||
WebSocket.prototype.close = function() {
|
||||
if (this.__createTask) {
|
||||
clearTimeout(this.__createTask);
|
||||
this.__createTask = null;
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
return;
|
||||
}
|
||||
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
||||
return;
|
||||
}
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
WebSocket.__flash.close(this.__id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {function} listener
|
||||
* @param {boolean} useCapture
|
||||
* @return void
|
||||
*/
|
||||
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
|
||||
if (!(type in this.__events)) {
|
||||
this.__events[type] = [];
|
||||
}
|
||||
this.__events[type].push(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {function} listener
|
||||
* @param {boolean} useCapture
|
||||
* @return void
|
||||
*/
|
||||
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
|
||||
if (!(type in this.__events)) return;
|
||||
var events = this.__events[type];
|
||||
for (var i = events.length - 1; i >= 0; --i) {
|
||||
if (events[i] === listener) {
|
||||
events.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
||||
*
|
||||
* @param {Event} event
|
||||
* @return void
|
||||
*/
|
||||
WebSocket.prototype.dispatchEvent = function(event) {
|
||||
var events = this.__events[event.type] || [];
|
||||
for (var i = 0; i < events.length; ++i) {
|
||||
events[i](event);
|
||||
}
|
||||
var handler = this["on" + event.type];
|
||||
if (handler) handler.apply(this, [event]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an event from Flash.
|
||||
* @param {Object} flashEvent
|
||||
*/
|
||||
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
||||
|
||||
if ("readyState" in flashEvent) {
|
||||
this.readyState = flashEvent.readyState;
|
||||
}
|
||||
if ("protocol" in flashEvent) {
|
||||
this.protocol = flashEvent.protocol;
|
||||
}
|
||||
|
||||
var jsEvent;
|
||||
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
||||
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
||||
} else if (flashEvent.type == "close") {
|
||||
jsEvent = this.__createSimpleEvent("close");
|
||||
jsEvent.wasClean = flashEvent.wasClean ? true : false;
|
||||
jsEvent.code = flashEvent.code;
|
||||
jsEvent.reason = flashEvent.reason;
|
||||
} else if (flashEvent.type == "message") {
|
||||
var data = decodeURIComponent(flashEvent.message);
|
||||
jsEvent = this.__createMessageEvent("message", data);
|
||||
} else {
|
||||
throw "unknown event type: " + flashEvent.type;
|
||||
}
|
||||
|
||||
this.dispatchEvent(jsEvent);
|
||||
|
||||
};
|
||||
|
||||
WebSocket.prototype.__createSimpleEvent = function(type) {
|
||||
if (document.createEvent && window.Event) {
|
||||
var event = document.createEvent("Event");
|
||||
event.initEvent(type, false, false);
|
||||
return event;
|
||||
} else {
|
||||
return {type: type, bubbles: false, cancelable: false};
|
||||
}
|
||||
};
|
||||
|
||||
WebSocket.prototype.__createMessageEvent = function(type, data) {
|
||||
if (document.createEvent && window.MessageEvent && !window.opera) {
|
||||
var event = document.createEvent("MessageEvent");
|
||||
event.initMessageEvent("message", false, false, data, null, null, window, null);
|
||||
return event;
|
||||
} else {
|
||||
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
|
||||
return {type: type, data: data, bubbles: false, cancelable: false};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the WebSocket readyState enumeration.
|
||||
*/
|
||||
WebSocket.CONNECTING = 0;
|
||||
WebSocket.OPEN = 1;
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.CLOSED = 3;
|
||||
|
||||
// Field to check implementation of WebSocket.
|
||||
WebSocket.__isFlashImplementation = true;
|
||||
WebSocket.__initialized = false;
|
||||
WebSocket.__flash = null;
|
||||
WebSocket.__instances = {};
|
||||
WebSocket.__tasks = [];
|
||||
WebSocket.__nextId = 0;
|
||||
|
||||
/**
|
||||
* Load a new flash security policy file.
|
||||
* @param {string} url
|
||||
*/
|
||||
WebSocket.loadFlashPolicyFile = function(url){
|
||||
WebSocket.__addTask(function() {
|
||||
WebSocket.__flash.loadManualPolicyFile(url);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
||||
*/
|
||||
WebSocket.__initialize = function() {
|
||||
|
||||
if (WebSocket.__initialized) return;
|
||||
WebSocket.__initialized = true;
|
||||
|
||||
if (WebSocket.__swfLocation) {
|
||||
// For backword compatibility.
|
||||
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
||||
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||
return;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
|
||||
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
|
||||
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
|
||||
var swfHost = RegExp.$1;
|
||||
if (location.host != swfHost) {
|
||||
logger.error(
|
||||
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
|
||||
"('" + location.host + "' != '" + swfHost + "'). " +
|
||||
"See also 'How to host HTML file and SWF file in different domains' section " +
|
||||
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
|
||||
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
|
||||
}
|
||||
}
|
||||
var container = document.createElement("div");
|
||||
container.id = "webSocketContainer";
|
||||
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
||||
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
|
||||
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
|
||||
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
|
||||
// the best we can do as far as we know now.
|
||||
container.style.position = "absolute";
|
||||
if (WebSocket.__isFlashLite()) {
|
||||
container.style.left = "0px";
|
||||
container.style.top = "0px";
|
||||
} else {
|
||||
container.style.left = "-100px";
|
||||
container.style.top = "-100px";
|
||||
}
|
||||
var holder = document.createElement("div");
|
||||
holder.id = "webSocketFlash";
|
||||
container.appendChild(holder);
|
||||
document.body.appendChild(container);
|
||||
// See this article for hasPriority:
|
||||
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
|
||||
swfobject.embedSWF(
|
||||
WEB_SOCKET_SWF_LOCATION,
|
||||
"webSocketFlash",
|
||||
"1" /* width */,
|
||||
"1" /* height */,
|
||||
"10.0.0" /* SWF version */,
|
||||
null,
|
||||
null,
|
||||
{hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
|
||||
null,
|
||||
function(e) {
|
||||
if (!e.success) {
|
||||
logger.error("[WebSocket] swfobject.embedSWF failed");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by Flash to notify JS that it's fully loaded and ready
|
||||
* for communication.
|
||||
*/
|
||||
WebSocket.__onFlashInitialized = function() {
|
||||
// We need to set a timeout here to avoid round-trip calls
|
||||
// to flash during the initialization process.
|
||||
setTimeout(function() {
|
||||
WebSocket.__flash = document.getElementById("webSocketFlash");
|
||||
WebSocket.__flash.setCallerUrl(location.href);
|
||||
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
|
||||
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
|
||||
WebSocket.__tasks[i]();
|
||||
}
|
||||
WebSocket.__tasks = [];
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by Flash to notify WebSockets events are fired.
|
||||
*/
|
||||
WebSocket.__onFlashEvent = function() {
|
||||
setTimeout(function() {
|
||||
try {
|
||||
// Gets events using receiveEvents() instead of getting it from event object
|
||||
// of Flash event. This is to make sure to keep message order.
|
||||
// It seems sometimes Flash events don't arrive in the same order as they are sent.
|
||||
var events = WebSocket.__flash.receiveEvents();
|
||||
for (var i = 0; i < events.length; ++i) {
|
||||
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}, 0);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__log = function(message) {
|
||||
logger.log(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__error = function(message) {
|
||||
logger.error(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
WebSocket.__addTask = function(task) {
|
||||
if (WebSocket.__flash) {
|
||||
task();
|
||||
} else {
|
||||
WebSocket.__tasks.push(task);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the browser is running flash lite.
|
||||
* @return {boolean} True if flash lite is running, false otherwise.
|
||||
*/
|
||||
WebSocket.__isFlashLite = function() {
|
||||
if (!window.navigator || !window.navigator.mimeTypes) {
|
||||
return false;
|
||||
}
|
||||
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
|
||||
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
|
||||
return false;
|
||||
}
|
||||
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
|
||||
};
|
||||
|
||||
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
||||
// NOTE:
|
||||
// This fires immediately if web_socket.js is dynamically loaded after
|
||||
// the document is loaded.
|
||||
swfobject.addDomLoadEvent(function() {
|
||||
WebSocket.__initialize();
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but Websock
|
||||
* enables communication with raw TCP sockets (i.e. the binary stream)
|
||||
* via websockify. This is accomplished by base64 encoding the data
|
||||
* stream between Websock and websockify.
|
||||
*
|
||||
* Websock has built-in receive queue buffering; the message event
|
||||
* does not contain actual data but is simply a notification that
|
||||
* there is new data available. Several rQ* methods are available to
|
||||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: false, plusplus: false */
|
||||
/*global Util, Base64 */
|
||||
|
||||
|
||||
// Load Flash WebSocket emulator if needed
|
||||
|
||||
// To force WebSocket emulator even when native WebSocket available
|
||||
//window.WEB_SOCKET_FORCE_FLASH = true;
|
||||
// To enable WebSocket emulator debug:
|
||||
//window.WEB_SOCKET_DEBUG=1;
|
||||
|
||||
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
window.WebSocket = window.MozWebSocket;
|
||||
} else {
|
||||
/* no builtin WebSocket so load web_socket.js */
|
||||
|
||||
Websock_native = false;
|
||||
(function () {
|
||||
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
|
||||
"web-socket-js/WebSocketMain.swf";
|
||||
if (Util.Engine.trident) {
|
||||
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
||||
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
}
|
||||
Util.load_scripts(["web-socket-js/swfobject.js",
|
||||
"web-socket-js/web_socket.js"]);
|
||||
}());
|
||||
}
|
||||
|
||||
|
||||
function Websock() {
|
||||
"use strict";
|
||||
|
||||
var api = {}, // Public API
|
||||
websocket = null, // WebSocket object
|
||||
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
|
||||
rQ = [], // Receive queue
|
||||
rQi = 0, // Receive queue index
|
||||
rQmax = 10000, // Max receive queue size before compacting
|
||||
sQ = [], // Send queue
|
||||
|
||||
eventHandlers = {
|
||||
'message' : function() {},
|
||||
'open' : function() {},
|
||||
'close' : function() {},
|
||||
'error' : function() {}
|
||||
},
|
||||
|
||||
test_mode = false;
|
||||
|
||||
|
||||
//
|
||||
// Queue public functions
|
||||
//
|
||||
|
||||
function get_sQ() {
|
||||
return sQ;
|
||||
}
|
||||
|
||||
function get_rQ() {
|
||||
return rQ;
|
||||
}
|
||||
function get_rQi() {
|
||||
return rQi;
|
||||
}
|
||||
function set_rQi(val) {
|
||||
rQi = val;
|
||||
}
|
||||
|
||||
function rQlen() {
|
||||
return rQ.length - rQi;
|
||||
}
|
||||
|
||||
function rQpeek8() {
|
||||
return (rQ[rQi] );
|
||||
}
|
||||
function rQshift8() {
|
||||
return (rQ[rQi++] );
|
||||
}
|
||||
function rQunshift8(num) {
|
||||
if (rQi === 0) {
|
||||
rQ.unshift(num);
|
||||
} else {
|
||||
rQi -= 1;
|
||||
rQ[rQi] = num;
|
||||
}
|
||||
|
||||
}
|
||||
function rQshift16() {
|
||||
return (rQ[rQi++] << 8) +
|
||||
(rQ[rQi++] );
|
||||
}
|
||||
function rQshift32() {
|
||||
return (rQ[rQi++] << 24) +
|
||||
(rQ[rQi++] << 16) +
|
||||
(rQ[rQi++] << 8) +
|
||||
(rQ[rQi++] );
|
||||
}
|
||||
function rQshiftStr(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
var arr = rQ.slice(rQi, rQi + len);
|
||||
rQi += len;
|
||||
return String.fromCharCode.apply(null, arr);
|
||||
}
|
||||
function rQshiftBytes(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
rQi += len;
|
||||
return rQ.slice(rQi-len, rQi);
|
||||
}
|
||||
|
||||
function rQslice(start, end) {
|
||||
if (end) {
|
||||
return rQ.slice(rQi + start, rQi + end);
|
||||
} else {
|
||||
return rQ.slice(rQi + start);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
function rQwait(msg, num, goback) {
|
||||
var rQlen = rQ.length - rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (rQi < goback) {
|
||||
throw("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
rQi -= goback;
|
||||
}
|
||||
//Util.Debug(" waiting for " + (num-rQlen) +
|
||||
// " " + msg + " byte(s)");
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Private utility routines
|
||||
//
|
||||
|
||||
function encode_message() {
|
||||
if (mode === 'binary') {
|
||||
// Put in a binary arraybuffer
|
||||
return (new Uint8Array(sQ)).buffer;
|
||||
} else {
|
||||
// base64 encode
|
||||
return Base64.encode(sQ);
|
||||
}
|
||||
}
|
||||
|
||||
function decode_message(data) {
|
||||
//Util.Debug(">> decode_message: " + data);
|
||||
if (mode === 'binary') {
|
||||
// push arraybuffer values onto the end
|
||||
var u8 = new Uint8Array(data);
|
||||
for (var i = 0; i < u8.length; i++) {
|
||||
rQ.push(u8[i]);
|
||||
}
|
||||
} else {
|
||||
// base64 decode and concat to the end
|
||||
rQ = rQ.concat(Base64.decode(data, 0));
|
||||
}
|
||||
//Util.Debug(">> decode_message, rQ: " + rQ);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public Send functions
|
||||
//
|
||||
|
||||
function flush() {
|
||||
if (websocket.bufferedAmount !== 0) {
|
||||
Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
|
||||
}
|
||||
if (websocket.bufferedAmount < api.maxBufferedAmount) {
|
||||
//Util.Debug("arr: " + arr);
|
||||
//Util.Debug("sQ: " + sQ);
|
||||
if (sQ.length > 0) {
|
||||
websocket.send(encode_message(sQ));
|
||||
sQ = [];
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Util.Info("Delaying send, bufferedAmount: " +
|
||||
websocket.bufferedAmount);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// overridable for testing
|
||||
function send(arr) {
|
||||
//Util.Debug(">> send_array: " + arr);
|
||||
sQ = sQ.concat(arr);
|
||||
return flush();
|
||||
}
|
||||
|
||||
function send_string(str) {
|
||||
//Util.Debug(">> send_string: " + str);
|
||||
api.send(str.split('').map(
|
||||
function (chr) { return chr.charCodeAt(0); } ) );
|
||||
}
|
||||
|
||||
//
|
||||
// Other public functions
|
||||
|
||||
function recv_message(e) {
|
||||
//Util.Debug(">> recv_message: " + e.data.length);
|
||||
|
||||
try {
|
||||
decode_message(e.data);
|
||||
if (rQlen() > 0) {
|
||||
eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (rQ.length > rQmax) {
|
||||
//Util.Debug("Compacting receive queue");
|
||||
rQ = rQ.slice(rQi);
|
||||
rQi = 0;
|
||||
}
|
||||
} else {
|
||||
Util.Debug("Ignoring empty message");
|
||||
}
|
||||
} catch (exc) {
|
||||
if (typeof exc.stack !== 'undefined') {
|
||||
Util.Warn("recv_message, caught exception: " + exc.stack);
|
||||
} else if (typeof exc.description !== 'undefined') {
|
||||
Util.Warn("recv_message, caught exception: " + exc.description);
|
||||
} else {
|
||||
Util.Warn("recv_message, caught exception:" + exc);
|
||||
}
|
||||
if (typeof exc.name !== 'undefined') {
|
||||
eventHandlers.error(exc.name + ": " + exc.message);
|
||||
} else {
|
||||
eventHandlers.error(exc);
|
||||
}
|
||||
}
|
||||
//Util.Debug("<< recv_message");
|
||||
}
|
||||
|
||||
|
||||
// Set event handlers
|
||||
function on(evt, handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
function init(protocols, ws_schema) {
|
||||
rQ = [];
|
||||
rQi = 0;
|
||||
sQ = [];
|
||||
websocket = null;
|
||||
|
||||
var bt = false,
|
||||
wsbt = false,
|
||||
try_binary = false;
|
||||
|
||||
// Check for full typed array support
|
||||
if (('Uint8Array' in window) &&
|
||||
('set' in Uint8Array.prototype)) {
|
||||
bt = true;
|
||||
}
|
||||
// Check for full binary type support in WebSocket
|
||||
// Inspired by:
|
||||
// https://github.com/Modernizr/Modernizr/issues/370
|
||||
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
|
||||
try {
|
||||
if (bt && ('binaryType' in WebSocket.prototype ||
|
||||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
|
||||
Util.Info("Detected binaryType support in WebSockets");
|
||||
wsbt = true;
|
||||
}
|
||||
} catch (exc) {
|
||||
// Just ignore failed test localhost connections
|
||||
}
|
||||
|
||||
// Default protocols if not specified
|
||||
if (typeof(protocols) === "undefined") {
|
||||
if (wsbt) {
|
||||
protocols = ['binary', 'base64'];
|
||||
} else {
|
||||
protocols = 'base64';
|
||||
}
|
||||
}
|
||||
|
||||
// If no binary support, make sure it was not requested
|
||||
if (!wsbt) {
|
||||
if (protocols === 'binary') {
|
||||
throw("WebSocket binary sub-protocol requested but not supported");
|
||||
}
|
||||
if (typeof(protocols) === "object") {
|
||||
var new_protocols = [];
|
||||
for (var i = 0; i < protocols.length; i++) {
|
||||
if (protocols[i] === 'binary') {
|
||||
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
|
||||
} else {
|
||||
new_protocols.push(protocols[i]);
|
||||
}
|
||||
}
|
||||
if (new_protocols.length > 0) {
|
||||
protocols = new_protocols;
|
||||
} else {
|
||||
throw("Only WebSocket binary sub-protocol was requested and not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
function open(uri, protocols) {
|
||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||
protocols = init(protocols, ws_schema);
|
||||
|
||||
if (test_mode) {
|
||||
websocket = {};
|
||||
} else {
|
||||
websocket = new WebSocket(uri, protocols);
|
||||
if (protocols.indexOf('binary') >= 0) {
|
||||
websocket.binaryType = 'arraybuffer';
|
||||
}
|
||||
}
|
||||
|
||||
websocket.onmessage = recv_message;
|
||||
websocket.onopen = function() {
|
||||
Util.Debug(">> WebSock.onopen");
|
||||
if (websocket.protocol) {
|
||||
mode = websocket.protocol;
|
||||
Util.Info("Server chose sub-protocol: " + websocket.protocol);
|
||||
} else {
|
||||
mode = 'base64';
|
||||
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
|
||||
}
|
||||
eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
};
|
||||
websocket.onclose = function(e) {
|
||||
Util.Debug(">> WebSock.onclose");
|
||||
eventHandlers.close(e);
|
||||
Util.Debug("<< WebSock.onclose");
|
||||
};
|
||||
websocket.onerror = function(e) {
|
||||
Util.Debug(">> WebSock.onerror: " + e);
|
||||
eventHandlers.error(e);
|
||||
Util.Debug("<< WebSock.onerror");
|
||||
};
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (websocket) {
|
||||
if ((websocket.readyState === WebSocket.OPEN) ||
|
||||
(websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Util.Info("Closing WebSocket connection");
|
||||
websocket.close();
|
||||
}
|
||||
websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
}
|
||||
|
||||
// Override internal functions for testing
|
||||
// Takes a send function, returns reference to recv function
|
||||
function testMode(override_send, data_mode) {
|
||||
test_mode = true;
|
||||
mode = data_mode;
|
||||
api.send = override_send;
|
||||
api.close = function () {};
|
||||
return recv_message;
|
||||
}
|
||||
|
||||
function constructor() {
|
||||
// Configuration settings
|
||||
api.maxBufferedAmount = 200;
|
||||
|
||||
// Direct access to send and receive queues
|
||||
api.get_sQ = get_sQ;
|
||||
api.get_rQ = get_rQ;
|
||||
api.get_rQi = get_rQi;
|
||||
api.set_rQi = set_rQi;
|
||||
|
||||
// Routines to read from the receive queue
|
||||
api.rQlen = rQlen;
|
||||
api.rQpeek8 = rQpeek8;
|
||||
api.rQshift8 = rQshift8;
|
||||
api.rQunshift8 = rQunshift8;
|
||||
api.rQshift16 = rQshift16;
|
||||
api.rQshift32 = rQshift32;
|
||||
api.rQshiftStr = rQshiftStr;
|
||||
api.rQshiftBytes = rQshiftBytes;
|
||||
api.rQslice = rQslice;
|
||||
api.rQwait = rQwait;
|
||||
|
||||
api.flush = flush;
|
||||
api.send = send;
|
||||
api.send_string = send_string;
|
||||
|
||||
api.on = on;
|
||||
api.init = init;
|
||||
api.open = open;
|
||||
api.close = close;
|
||||
api.testMode = testMode;
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
return constructor();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* from noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
/*jslint bitwise: false, white: false */
|
||||
/*global Util, window, document */
|
||||
|
||||
// Globals defined here
|
||||
var WebUtil = {}, $D;
|
||||
|
||||
/*
|
||||
* Simple DOM selector by ID
|
||||
*/
|
||||
if (!window.$D) {
|
||||
window.$D = function (id) {
|
||||
if (document.getElementById) {
|
||||
return document.getElementById(id);
|
||||
} else if (document.all) {
|
||||
return document.all[id];
|
||||
} else if (document.layers) {
|
||||
return document.layers[id];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in WebUtil
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
WebUtil.init_logging = function(level) {
|
||||
if (typeof level !== "undefined") {
|
||||
Util._log_level = level;
|
||||
} else {
|
||||
Util._log_level = (document.location.href.match(
|
||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||
['', Util._log_level])[1];
|
||||
}
|
||||
Util.init_logging();
|
||||
};
|
||||
|
||||
|
||||
WebUtil.dirObj = function (obj, depth, parent) {
|
||||
var i, msg = "", val = "";
|
||||
if (! depth) { depth=2; }
|
||||
if (! parent) { parent= ""; }
|
||||
|
||||
// Print the properties of the passed-in object
|
||||
for (i in obj) {
|
||||
if ((depth > 1) && (typeof obj[i] === "object")) {
|
||||
// Recurse attributes that are objects
|
||||
msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
|
||||
} else {
|
||||
//val = new String(obj[i]).replace("\n", " ");
|
||||
if (typeof(obj[i]) === "undefined") {
|
||||
val = "undefined";
|
||||
} else {
|
||||
val = obj[i].toString().replace("\n", " ");
|
||||
}
|
||||
if (val.length > 30) {
|
||||
val = val.substr(0,30) + "...";
|
||||
}
|
||||
msg += parent + "." + i + ": " + val + "\n";
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
// Read a query string variable
|
||||
WebUtil.getQueryVar = function(name, defVal) {
|
||||
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
} else {
|
||||
return defVal;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
|
||||
*/
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.createCookie = function(name,value,days) {
|
||||
var date, expires;
|
||||
if (days) {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime()+(days*24*60*60*1000));
|
||||
expires = "; expires="+date.toGMTString();
|
||||
}
|
||||
else {
|
||||
expires = "";
|
||||
}
|
||||
document.cookie = name+"="+value+expires+"; path=/";
|
||||
};
|
||||
|
||||
WebUtil.readCookie = function(name, defaultValue) {
|
||||
var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
|
||||
for(i=0; i < ca.length; i += 1) {
|
||||
c = ca[i];
|
||||
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
|
||||
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
|
||||
}
|
||||
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
|
||||
};
|
||||
|
||||
WebUtil.eraseCookie = function(name) {
|
||||
WebUtil.createCookie(name,"",-1);
|
||||
};
|
||||
|
||||
/*
|
||||
* Setting handling.
|
||||
*/
|
||||
|
||||
WebUtil.initSettings = function(callback) {
|
||||
var callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get(function (cfg) {
|
||||
WebUtil.settings = cfg;
|
||||
console.log(WebUtil.settings);
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No-op
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.writeSetting = function(name, value) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
//console.log("writeSetting:", name, value);
|
||||
if (WebUtil.settings[name] !== value) {
|
||||
WebUtil.settings[name] = value;
|
||||
window.chrome.storage.sync.set(WebUtil.settings);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.readSetting = function(name, defaultValue) {
|
||||
var value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
value = WebUtil.settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
value = null;
|
||||
}
|
||||
if (value === null && typeof defaultValue !== undefined) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.eraseSetting = function(name) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
delete WebUtil.settings[name];
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Alternate stylesheet selection
|
||||
*/
|
||||
WebUtil.getStylesheets = function() { var i, links, sheets = [];
|
||||
links = document.getElementsByTagName("link");
|
||||
for (i = 0; i < links.length; i += 1) {
|
||||
if (links[i].title &&
|
||||
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
|
||||
sheets.push(links[i]);
|
||||
}
|
||||
}
|
||||
return sheets;
|
||||
};
|
||||
|
||||
// No sheet means try and use value from cookie, null sheet used to
|
||||
// clear all alternates.
|
||||
WebUtil.selectStylesheet = function(sheet) {
|
||||
var i, link, sheets = WebUtil.getStylesheets();
|
||||
if (typeof sheet === 'undefined') {
|
||||
sheet = 'default';
|
||||
}
|
||||
for (i=0; i < sheets.length; i += 1) {
|
||||
link = sheets[i];
|
||||
if (link.title === sheet) {
|
||||
Util.Debug("Using stylesheet " + sheet);
|
||||
link.disabled = false;
|
||||
} else {
|
||||
//Util.Debug("Skipping stylesheet " + link.title);
|
||||
link.disabled = true;
|
||||
}
|
||||
}
|
||||
return sheet;
|
||||
};
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* WebSockets IRC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
*
|
||||
* Includes VT100.js from:
|
||||
* http://code.google.com/p/sshconsole
|
||||
* Which was modified from:
|
||||
* http://fzort.org/bi/o.php#vt100_js
|
||||
|
||||
* IRC Client protocol:
|
||||
* http://www.faqs.org/rfcs/rfc2812.html
|
||||
*/
|
||||
|
||||
|
||||
function IRC(target, connect_callback, disconnect_callback) {
|
||||
|
||||
var that = {}, // Public API interface
|
||||
vt100, ws, sQ = [],
|
||||
state = "unconnected",
|
||||
irc_nick, irc_channel,
|
||||
termType = "VT100";
|
||||
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function do_send() {
|
||||
if (sQ.length > 0) {
|
||||
Util.Debug("Sending " + sQ);
|
||||
ws.send(sQ);
|
||||
sQ = [];
|
||||
}
|
||||
}
|
||||
|
||||
function do_recv() {
|
||||
console.log(">> do_recv");
|
||||
var rQ, rQi, i;
|
||||
|
||||
while (ws.rQlen() > 1) {
|
||||
rQ = ws.get_rQ();
|
||||
rQi = ws.get_rQi();
|
||||
for (i = rQi; i < rQ.length; i++) {
|
||||
if (rQ[i] === 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= rQ.length) {
|
||||
// No line break found
|
||||
break;
|
||||
}
|
||||
recvMsg(ws.rQshiftStr((i-rQi) + 1));
|
||||
}
|
||||
//console.log("<< do_recv");
|
||||
}
|
||||
|
||||
// Handle an IRC message
|
||||
function recvMsg(msg) {
|
||||
Util.Debug(">> recvMsg('" + msg + "')");
|
||||
|
||||
var tokens = msg.split(' '), in_params = true,
|
||||
prefix, command, params = [], trailing = [];
|
||||
|
||||
Util.Info(" tokens: " + tokens);
|
||||
|
||||
if (tokens[0].charAt(0) === ":") {
|
||||
prefix = tokens.shift();
|
||||
}
|
||||
|
||||
command = tokens.shift();
|
||||
|
||||
while (tokens.length > 0) {
|
||||
if (tokens[0].charAt(0) === ":") {
|
||||
in_params = false;
|
||||
}
|
||||
if (in_params) {
|
||||
params.push(tokens.shift());
|
||||
} else {
|
||||
trailing.push(tokens.shift());
|
||||
}
|
||||
}
|
||||
|
||||
Util.Info(" prefix: " + prefix);
|
||||
Util.Info(" command: " + command);
|
||||
Util.Info(" params: " + params);
|
||||
Util.Info(" trailing: " + trailing);
|
||||
|
||||
// Show raw received
|
||||
vt100.write(msg);
|
||||
|
||||
switch (command) {
|
||||
case "004":
|
||||
state = "registered";
|
||||
vt100.write("Joining channel #" + irc_channel);
|
||||
sendCmd("JOIN #" + irc_channel);
|
||||
break;
|
||||
case "JOIN":
|
||||
state = "joined";
|
||||
vt100.write("Joined channel #" + irc_channel);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
Util.Debug("<< recvMsg('" + msg + "')");
|
||||
}
|
||||
|
||||
function sendCmd(msg) {
|
||||
Util.Info("Sending: " + msg);
|
||||
sQ.pushStr(msg + "\r\n");
|
||||
do_send();
|
||||
}
|
||||
|
||||
that.sendMsg = function(msg) {
|
||||
// TODO parse into message
|
||||
sendCmd("PRIVMSG #" + irc_channel + " :" + msg);
|
||||
}
|
||||
|
||||
|
||||
that.connect = function(host, port, encrypt, nick, channel) {
|
||||
var host = host,
|
||||
port = port,
|
||||
scheme = "ws://", uri;
|
||||
|
||||
irc_nick = nick;
|
||||
irc_channel = channel;
|
||||
|
||||
Util.Debug(">> connect");
|
||||
if ((!host) || (!port)) {
|
||||
alert("must set host and port");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (encrypt) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
uri = scheme + host + ":" + port;
|
||||
Util.Info("connecting to " + uri);
|
||||
|
||||
ws.open(uri);
|
||||
|
||||
Util.Debug("<< connect");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
that.disconnect = function() {
|
||||
Util.Debug(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
disconnect_callback();
|
||||
Util.Debug("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
function constructor() {
|
||||
/* Initialize Websock object */
|
||||
ws = new Websock();
|
||||
|
||||
ws.on('message', do_recv);
|
||||
ws.on('open', function(e) {
|
||||
Util.Info(">> WebSockets.onopen");
|
||||
// Send registration commands
|
||||
state = "connected";
|
||||
sendCmd("NICK " + irc_nick);
|
||||
// TODO: how to determine this?
|
||||
sendCmd("USER joelm 0 * :Joel Martin");
|
||||
connect_callback();
|
||||
Util.Info("<< WebSockets.onopen");
|
||||
});
|
||||
ws.on('close', function(e) {
|
||||
Util.Info(">> WebSockets.onclose");
|
||||
that.disconnect();
|
||||
Util.Info("<< WebSockets.onclose");
|
||||
});
|
||||
ws.on('error', function(e) {
|
||||
Util.Info(">> WebSockets.onerror");
|
||||
that.disconnect();
|
||||
Util.Info("<< WebSockets.onerror");
|
||||
});
|
||||
|
||||
/* Initialize the terminal emulator/renderer */
|
||||
|
||||
vt100 = new VT100(80, 24, target);
|
||||
|
||||
// Show cursor
|
||||
vt100.curs_set(true, false);
|
||||
|
||||
/*
|
||||
* Override VT100 I/O routines
|
||||
*/
|
||||
|
||||
// Set handler for sending characters
|
||||
vt100.getch(
|
||||
function send_chr(chr, vt) {
|
||||
var i;
|
||||
Util.Debug(">> send_chr: " + chr);
|
||||
for (i = 0; i < chr.length; i++) {
|
||||
sQ.push(chr.charCodeAt(i));
|
||||
}
|
||||
do_send();
|
||||
vt100.getch(send_chr);
|
||||
}
|
||||
);
|
||||
|
||||
vt100.debug = function(message) {
|
||||
Util.Debug(message + "\n");
|
||||
}
|
||||
|
||||
vt100.warn = function(message) {
|
||||
Util.Warn(message + "\n");
|
||||
}
|
||||
|
||||
vt100.curs_set = function(vis, grab, eventist)
|
||||
{
|
||||
this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
|
||||
if (vis !== undefined)
|
||||
this.cursor_vis_ = (vis > 0);
|
||||
}
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
return constructor(); // Return the public API interface
|
||||
|
||||
} // End of Telnet()
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* WebSockets telnet client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
*
|
||||
* Includes VT100.js from:
|
||||
* http://code.google.com/p/sshconsole
|
||||
* Which was modified from:
|
||||
* http://fzort.org/bi/o.php#vt100_js
|
||||
*
|
||||
* Telnet protocol:
|
||||
* http://www.networksorcery.com/enp/protocol/telnet.htm
|
||||
* http://www.networksorcery.com/enp/rfc/rfc1091.txt
|
||||
*
|
||||
* ANSI escape sequeneces:
|
||||
* http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
* http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||
* http://www.termsys.demon.co.uk/vtansi.htm
|
||||
* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
*
|
||||
* ASCII codes:
|
||||
* http://en.wikipedia.org/wiki/ASCII
|
||||
* http://www.hobbyprojects.com/ascii-table/ascii-table.html
|
||||
*
|
||||
* Other web consoles:
|
||||
* http://stackoverflow.com/questions/244750/ajax-console-window-with-ansi-vt100-support
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
function Telnet(target, connect_callback, disconnect_callback) {
|
||||
|
||||
var that = {}, // Public API interface
|
||||
vt100, ws, sQ = [];
|
||||
termType = "VT100";
|
||||
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function do_send() {
|
||||
if (sQ.length > 0) {
|
||||
Util.Debug("Sending " + sQ);
|
||||
ws.send(sQ);
|
||||
sQ = [];
|
||||
}
|
||||
}
|
||||
|
||||
function do_recv() {
|
||||
//console.log(">> do_recv");
|
||||
var arr = ws.rQshiftBytes(ws.rQlen()), str = "",
|
||||
chr, cmd, code, value;
|
||||
|
||||
Util.Debug("Received array '" + arr + "'");
|
||||
while (arr.length > 0) {
|
||||
chr = arr.shift();
|
||||
switch (chr) {
|
||||
case 255: // IAC
|
||||
cmd = chr;
|
||||
code = arr.shift();
|
||||
value = arr.shift();
|
||||
switch (code) {
|
||||
case 254: // DONT
|
||||
Util.Debug("Got Cmd DONT '" + value + "', ignoring");
|
||||
break;
|
||||
case 253: // DO
|
||||
Util.Debug("Got Cmd DO '" + value + "'");
|
||||
if (value === 24) {
|
||||
// Terminal type
|
||||
Util.Info("Send WILL '" + value + "' (TERM-TYPE)");
|
||||
sQ.push(255, 251, value);
|
||||
} else {
|
||||
// Refuse other DO requests with a WONT
|
||||
Util.Debug("Send WONT '" + value + "'");
|
||||
sQ.push(255, 252, value);
|
||||
}
|
||||
break;
|
||||
case 252: // WONT
|
||||
Util.Debug("Got Cmd WONT '" + value + "', ignoring");
|
||||
break;
|
||||
case 251: // WILL
|
||||
Util.Debug("Got Cmd WILL '" + value + "'");
|
||||
if (value === 1) {
|
||||
// Server will echo, turn off local echo
|
||||
vt100.noecho();
|
||||
// Affirm echo with DO
|
||||
Util.Info("Send Cmd DO '" + value + "' (echo)");
|
||||
sQ.push(255, 253, value);
|
||||
} else {
|
||||
// Reject other WILL offers with a DONT
|
||||
Util.Debug("Send Cmd DONT '" + value + "'");
|
||||
sQ.push(255, 254, value);
|
||||
}
|
||||
break;
|
||||
case 250: // SB (subnegotiation)
|
||||
if (value === 24) {
|
||||
Util.Info("Got IAC SB TERM-TYPE SEND(1) IAC SE");
|
||||
// TERM-TYPE subnegotiation
|
||||
if (arr[0] === 1 &&
|
||||
arr[1] === 255 &&
|
||||
arr[2] === 240) {
|
||||
arr.shift(); arr.shift(); arr.shift();
|
||||
Util.Info("Send IAC SB TERM-TYPE IS(0) '" +
|
||||
termType + "' IAC SE");
|
||||
sQ.push(255, 250, 24, 0);
|
||||
sQ.pushStr(termType);
|
||||
sQ.push(255, 240);
|
||||
} else {
|
||||
Util.Info("Invalid subnegotiation received" + arr);
|
||||
}
|
||||
} else {
|
||||
Util.Info("Ignoring SB " + value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Util.Info("Got Cmd " + cmd + " " + value + ", ignoring"); }
|
||||
continue;
|
||||
case 242: // Data Mark (Synch)
|
||||
cmd = chr;
|
||||
code = arr.shift();
|
||||
value = arr.shift();
|
||||
Util.Info("Ignoring Data Mark (Synch)");
|
||||
break;
|
||||
default: // everything else
|
||||
str += String.fromCharCode(chr);
|
||||
}
|
||||
}
|
||||
|
||||
if (sQ) {
|
||||
do_send();
|
||||
}
|
||||
|
||||
if (str) {
|
||||
vt100.write(str);
|
||||
}
|
||||
|
||||
//console.log("<< do_recv");
|
||||
}
|
||||
|
||||
|
||||
|
||||
that.connect = function(host, port, encrypt) {
|
||||
var host = host,
|
||||
port = port,
|
||||
scheme = "ws://", uri;
|
||||
|
||||
Util.Debug(">> connect");
|
||||
if ((!host) || (!port)) {
|
||||
console.log("must set host and port");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (encrypt) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
uri = scheme + host + ":" + port;
|
||||
Util.Info("connecting to " + uri);
|
||||
|
||||
ws.open(uri);
|
||||
|
||||
Util.Debug("<< connect");
|
||||
}
|
||||
|
||||
that.disconnect = function() {
|
||||
Util.Debug(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
vt100.curs_set(true, false);
|
||||
|
||||
disconnect_callback();
|
||||
Util.Debug("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
function constructor() {
|
||||
/* Initialize Websock object */
|
||||
ws = new Websock();
|
||||
|
||||
ws.on('message', do_recv);
|
||||
ws.on('open', function(e) {
|
||||
Util.Info(">> WebSockets.onopen");
|
||||
vt100.curs_set(true, true);
|
||||
connect_callback();
|
||||
Util.Info("<< WebSockets.onopen");
|
||||
});
|
||||
ws.on('close', function(e) {
|
||||
Util.Info(">> WebSockets.onclose");
|
||||
that.disconnect();
|
||||
Util.Info("<< WebSockets.onclose");
|
||||
});
|
||||
ws.on('error', function(e) {
|
||||
Util.Info(">> WebSockets.onerror");
|
||||
that.disconnect();
|
||||
Util.Info("<< WebSockets.onerror");
|
||||
});
|
||||
|
||||
/* Initialize the terminal emulator/renderer */
|
||||
|
||||
vt100 = new VT100(80, 24, target);
|
||||
|
||||
|
||||
/*
|
||||
* Override VT100 I/O routines
|
||||
*/
|
||||
|
||||
// Set handler for sending characters
|
||||
vt100.getch(
|
||||
function send_chr(chr, vt) {
|
||||
var i;
|
||||
Util.Debug(">> send_chr: " + chr);
|
||||
for (i = 0; i < chr.length; i++) {
|
||||
sQ.push(chr.charCodeAt(i));
|
||||
}
|
||||
do_send();
|
||||
vt100.getch(send_chr);
|
||||
}
|
||||
);
|
||||
|
||||
vt100.debug = function(message) {
|
||||
Util.Debug(message + "\n");
|
||||
}
|
||||
|
||||
vt100.warn = function(message) {
|
||||
Util.Warn(message + "\n");
|
||||
}
|
||||
|
||||
vt100.curs_set = function(vis, grab, eventist)
|
||||
{
|
||||
this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
|
||||
if (vis !== undefined)
|
||||
this.cursor_vis_ = (vis > 0);
|
||||
if (eventist === undefined)
|
||||
eventist = window;
|
||||
if (grab === true || grab === false) {
|
||||
if (grab === this.grab_events_)
|
||||
return;
|
||||
if (grab) {
|
||||
this.grab_events_ = true;
|
||||
VT100.the_vt_ = this;
|
||||
Util.addEvent(eventist, 'keydown', vt100.key_down);
|
||||
Util.addEvent(eventist, 'keyup', vt100.key_up);
|
||||
} else {
|
||||
Util.removeEvent(eventist, 'keydown', vt100.key_down);
|
||||
Util.removeEvent(eventist, 'keyup', vt100.key_up);
|
||||
this.grab_events_ = false;
|
||||
VT100.the_vt_ = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vt100.key_down = function(e) {
|
||||
var vt = VT100.the_vt_, keysym, ch, str = "";
|
||||
|
||||
if (vt === undefined)
|
||||
return true;
|
||||
|
||||
keysym = getKeysym(e);
|
||||
|
||||
if (keysym < 128) {
|
||||
if (e.ctrlKey) {
|
||||
if (keysym == 64) {
|
||||
// control 0
|
||||
ch = 0;
|
||||
} else if ((keysym >= 97) && (keysym <= 122)) {
|
||||
// control codes 1-26
|
||||
ch = keysym - 96;
|
||||
} else if ((keysym >= 91) && (keysym <= 95)) {
|
||||
// control codes 27-31
|
||||
ch = keysym - 64;
|
||||
} else {
|
||||
Util.Info("Debug unknown control keysym: " + keysym);
|
||||
}
|
||||
} else {
|
||||
ch = keysym;
|
||||
}
|
||||
str = String.fromCharCode(ch);
|
||||
} else {
|
||||
switch (keysym) {
|
||||
case 65505: // Shift, do not send directly
|
||||
break;
|
||||
case 65507: // Ctrl, do not send directly
|
||||
break;
|
||||
case 65293: // Carriage return, line feed
|
||||
str = '\n'; break;
|
||||
case 65288: // Backspace
|
||||
str = '\b'; break;
|
||||
case 65307: // Escape
|
||||
str = '\x1b'; break;
|
||||
case 65361: // Left arrow
|
||||
str = '\x1b[D'; break;
|
||||
case 65362: // Up arrow
|
||||
str = '\x1b[A'; break;
|
||||
case 65363: // Right arrow
|
||||
str = '\x1b[C'; break;
|
||||
case 65364: // Down arrow
|
||||
str = '\x1b[B'; break;
|
||||
default:
|
||||
Util.Info("Unrecoginized keysym " + keysym);
|
||||
}
|
||||
}
|
||||
|
||||
if (str) {
|
||||
vt.key_buf_.push(str);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
}
|
||||
|
||||
Util.stopEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
vt100.key_up = function(e) {
|
||||
var vt = VT100.the_vt_;
|
||||
if (vt === undefined)
|
||||
return true;
|
||||
Util.stopEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
return constructor(); // Return the public API interface
|
||||
|
||||
} // End of Telnet()
|
||||
Reference in New Issue
Block a user