|
|
- /**
- * Copyright (c) 2013 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- **/
-
- var http = function() {
-
- if (!chrome.sockets || !chrome.sockets.tcpServer)
- return {};
-
- // Wrap chrome.sockets.tcp socketId with a Promise API.
- var PSocket = (function() {
- // chrome.sockets.tcp uses a global listener for incoming data so
- // use a map to dispatch to the proper instance.
- var socketMap = {};
- chrome.sockets.tcp.onReceive.addListener(function(info) {
- var pSocket = socketMap[info.socketId];
- if (pSocket) {
- if (pSocket.handlers) {
- // Fulfil the pending read.
- pSocket.handlers.resolve(info.data);
- delete pSocket.handlers;
- }
- else {
- // No pending read so put data on the queue.
- pSocket.readQueue.push(info);
- }
- }
- });
-
- // Read errors also use a global listener.
- chrome.sockets.tcp.onReceiveError.addListener(function(info) {
- var pSocket = socketMap[info.socketId];
- if (pSocket) {
- if (pSocket.handlers) {
- // Reject the pending read.
- pSocket.handlers.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
- delete pSocket.handlers;
- }
- else {
- // No pending read so put data on the queue.
- pSocket.readQueue.push(info);
- }
- }
- });
-
- // PSocket constructor.
- return function(socketId) {
- this.socketId = socketId;
- this.readQueue = [];
-
- // Register this instance for incoming data processing.
- socketMap[socketId] = this;
- chrome.sockets.tcp.setPaused(socketId, false);
- };
- })();
-
- // Returns a Promise<ArrayBuffer> with read data.
- PSocket.prototype.read = function() {
- var that = this;
- if (this.readQueue.length) {
- // Return data from the queue.
- var info = this.readQueue.shift();
- if (!info.resultCode)
- return Promise.resolve(info.data);
- else
- return Promise.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
- }
- else {
- // The queue is empty so install handlers.
- return new Promise(function(resolve, reject) {
- that.handlers = { resolve: resolve, reject: reject };
- });
- }
- };
-
- // Returns a Promise<integer> with the number of bytes written.
- PSocket.prototype.write = function(data) {
- var that = this;
- return new Promise(function(resolve, reject) {
- chrome.sockets.tcp.send(that.socketId, data, function(info) {
- if (info && info.resultCode >= 0)
- resolve(info.bytesSent);
- else
- reject(new Error('chrome sockets.tcp error ' + (info && info.resultCode)));
- });
- });
- };
-
- // Returns a Promise.
- PSocket.prototype.close = function() {
- var that = this;
- return new Promise(function(resolve, reject) {
- chrome.sockets.tcp.disconnect(that.socketId, function() {
- chrome.sockets.tcp.close(that.socketId, resolve);
- });
- });
- };
-
- // Http response code strings.
- var responseMap = {
- 200: 'OK',
- 301: 'Moved Permanently',
- 304: 'Not Modified',
- 400: 'Bad Request',
- 401: 'Unauthorized',
- 403: 'Forbidden',
- 404: 'Not Found',
- 413: 'Request Entity Too Large',
- 414: 'Request-URI Too Long',
- 500: 'Internal Server Error'};
-
- /**
- * Convert from an ArrayBuffer to a string.
- * @param {ArrayBuffer} buffer The array buffer to convert.
- * @return {string} The textual representation of the array.
- */
- var arrayBufferToString = function(buffer) {
- var array = new Uint8Array(buffer);
- var str = '';
- for (var i = 0; i < array.length; ++i) {
- str += String.fromCharCode(array[i]);
- }
- return str;
- };
-
- /**
- * Convert from an UTF-8 array to UTF-8 string.
- * @param {array} UTF-8 array
- * @return {string} UTF-8 string
- */
- var ary2utf8 = (function() {
-
- var patterns = [
- {pattern: '0xxxxxxx', bytes: 1},
- {pattern: '110xxxxx', bytes: 2},
- {pattern: '1110xxxx', bytes: 3},
- {pattern: '11110xxx', bytes: 4},
- {pattern: '111110xx', bytes: 5},
- {pattern: '1111110x', bytes: 6}
- ];
- patterns.forEach(function(item) {
- item.header = item.pattern.replace(/[^10]/g, '');
- item.pattern01 = item.pattern.replace(/[^10]/g, '0');
- item.pattern01 = parseInt(item.pattern01, 2);
- item.mask_length = item.header.length;
- item.data_length = 8 - item.header.length;
- var mask = '';
- for (var i = 0, len = item.mask_length; i < len; i++) {
- mask += '1';
- }
- for (var i = 0, len = item.data_length; i < len; i++) {
- mask += '0';
- }
- item.mask = mask;
- item.mask = parseInt(item.mask, 2);
- });
-
- return function(ary) {
- var codes = [];
- var cur = 0;
- while(cur < ary.length) {
- var first = ary[cur];
- var pattern = null;
- for (var i = 0, len = patterns.length; i < len; i++) {
- if ((first & patterns[i].mask) == patterns[i].pattern01) {
- pattern = patterns[i];
- break;
- }
- }
- if (pattern == null) {
- throw 'utf-8 decode error';
- }
- var rest = ary.slice(cur + 1, cur + pattern.bytes);
- cur += pattern.bytes;
- var code = '';
- code += ('00000000' + (first & (255 ^ pattern.mask)).toString(2)).slice(-pattern.data_length);
- for (var i = 0, len = rest.length; i < len; i++) {
- code += ('00000000' + (rest[i] & parseInt('111111', 2)).toString(2)).slice(-6);
- }
- codes.push(parseInt(code, 2));
- }
- return String.fromCharCode.apply(null, codes);
- };
-
- })();
-
- /**
- * Convert from an UTF-8 string to UTF-8 array.
- * @param {string} UTF-8 string
- * @return {array} UTF-8 array
- */
- var utf82ary = (function() {
-
- var patterns = [
- {pattern: '0xxxxxxx', bytes: 1},
- {pattern: '110xxxxx', bytes: 2},
- {pattern: '1110xxxx', bytes: 3},
- {pattern: '11110xxx', bytes: 4},
- {pattern: '111110xx', bytes: 5},
- {pattern: '1111110x', bytes: 6}
- ];
- patterns.forEach(function(item) {
- item.header = item.pattern.replace(/[^10]/g, '');
- item.mask_length = item.header.length;
- item.data_length = 8 - item.header.length;
- item.max_bit_length = (item.bytes - 1) * 6 + item.data_length;
- });
-
- var code2utf8array = function(code) {
- var pattern = null;
- var code01 = code.toString(2);
- for (var i = 0, len = patterns.length; i < len; i++) {
- if (code01.length <= patterns[i].max_bit_length) {
- pattern = patterns[i];
- break;
- }
- }
- if (pattern == null) {
- throw 'utf-8 encode error';
- }
- var ary = [];
- for (var i = 0, len = pattern.bytes - 1; i < len; i++) {
- ary.unshift(parseInt('10' + ('000000' + code01.slice(-6)).slice(-6), 2));
- code01 = code01.slice(0, -6);
- }
- ary.unshift(parseInt(pattern.header + ('00000000' + code01).slice(-pattern.data_length), 2));
- return ary;
- };
-
- return function(str) {
- var codes = [];
- for (var i = 0, len = str.length; i < len; i++) {
- var code = str.charCodeAt(i);
- Array.prototype.push.apply(codes, code2utf8array(code));
- }
- return codes;
- };
-
- })();
-
- /**
- * Convert a string to an ArrayBuffer.
- * @param {string} string The string to convert.
- * @return {ArrayBuffer} An array buffer whose bytes correspond to the string.
- */
- var stringToArrayBuffer = function(string) {
- var buffer = new ArrayBuffer(string.length);
- var bufferView = new Uint8Array(buffer);
- for (var i = 0; i < string.length; i++) {
- bufferView[i] = string.charCodeAt(i);
- }
- return buffer;
- };
-
- /**
- * An event source can dispatch events. These are dispatched to all of the
- * functions listening for that event type with arguments.
- * @constructor
- */
- function EventSource() {
- this.listeners_ = {};
- };
-
- EventSource.prototype = {
- /**
- * Add |callback| as a listener for |type| events.
- * @param {string} type The type of the event.
- * @param {function(Object|undefined): boolean} callback The function to call
- * when this event type is dispatched. Arguments depend on the event
- * source and type. The function returns whether the event was "handled"
- * which will prevent delivery to the rest of the listeners.
- */
- addEventListener: function(type, callback) {
- if (!this.listeners_[type])
- this.listeners_[type] = [];
- this.listeners_[type].push(callback);
- },
-
- /**
- * Remove |callback| as a listener for |type| events.
- * @param {string} type The type of the event.
- * @param {function(Object|undefined): boolean} callback The callback
- * function to remove from the event listeners for events having type
- * |type|.
- */
- removeEventListener: function(type, callback) {
- if (!this.listeners_[type])
- return;
- for (var i = this.listeners_[type].length - 1; i >= 0; i--) {
- if (this.listeners_[type][i] == callback) {
- this.listeners_[type].splice(i, 1);
- }
- }
- },
-
- /**
- * Dispatch an event to all listeners for events of type |type|.
- * @param {type} type The type of the event being dispatched.
- * @param {...Object} var_args The arguments to pass when calling the
- * callback function.
- * @return {boolean} Returns true if the event was handled.
- */
- dispatchEvent: function(type, var_args) {
- if (!this.listeners_[type])
- return false;
- for (var i = 0; i < this.listeners_[type].length; i++) {
- if (this.listeners_[type][i].apply(
- /* this */ null,
- /* var_args */ Array.prototype.slice.call(arguments, 1))) {
- return true;
- }
- }
- }
- };
-
- /**
- * HttpServer provides a lightweight Http web server. Currently it only
- * supports GET requests and upgrading to other protocols (i.e. WebSockets).
- * @constructor
- */
- function HttpServer() {
- EventSource.apply(this);
- this.readyState_ = 0;
- }
-
- HttpServer.prototype = {
- __proto__: EventSource.prototype,
-
- /**
- * Listen for connections on |port| using the interface |host|.
- * @param {number} port The port to listen for incoming connections on.
- * @param {string=} opt_host The host interface to listen for connections on.
- * This will default to 0.0.0.0 if not specified which will listen on
- * all interfaces.
- */
- listen: function(port, opt_host) {
- var t = this;
- chrome.sockets.tcpServer.create(function(socketInfo) {
- chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) {
- if (acceptInfo.socketId === socketInfo.socketId)
- t.readRequestFromSocket_(new PSocket(acceptInfo.clientSocketId));
- });
- this.socketId = socketInfo.socketId;
- chrome.runtime.sendMessage({socketId: this.socketId});
- chrome.sockets.tcpServer.listen(
- socketInfo.socketId,
- opt_host || '0.0.0.0',
- port,
- 50,
- function(result) {
- if (!result) {
- t.readyState_ = 1;
- }
- else {
- console.log(
- 'listen error ' +
- chrome.runtime.lastError.message +
- ' (normal if another instance is already serving requests)');
- }
- });
- });
- },
-
- close: function() {
- if (this.socketId) {
- chrome.sockets.tcpServer.close(this.socketId);
- this.socketId = 0;
- }
- },
- readRequestFromSocket_: function(pSocket) {
- var t = this;
- var requestData = '';
- var endIndex = 0;
- var onDataRead = function(data) {
- requestData += arrayBufferToString(data).replace(/\r\n/g, '\n');
- // Check for end of request.
- endIndex = requestData.indexOf('\n\n', endIndex);
- if (endIndex == -1) {
- endIndex = requestData.length - 1;
- return pSocket.read().then(onDataRead);
- }
-
- var headers = requestData.substring(0, endIndex).split('\n');
- var headerMap = {};
- // headers[0] should be the Request-Line
- var requestLine = headers[0].split(' ');
- headerMap['method'] = requestLine[0];
- headerMap['url'] = requestLine[1];
- headerMap['Http-Version'] = requestLine[2];
- for (var i = 1; i < headers.length; i++) {
- requestLine = headers[i].split(':', 2);
- if (requestLine.length == 2)
- headerMap[requestLine[0]] = requestLine[1].trim();
- }
- var request = new HttpRequest(headerMap, pSocket);
- t.onRequest_(request);
- };
-
- pSocket.read().then(onDataRead).catch(function(e) {
- pSocket.close();
- });
- },
-
- onRequest_: function(request) {
- var type = request.headers['Upgrade'] ? 'upgrade' : 'request';
- var keepAlive = request.headers['Connection'] == 'keep-alive';
- if (!this.dispatchEvent(type, request))
- request.close();
- else if (keepAlive)
- this.readRequestFromSocket_(request.pSocket_);
- },
- };
-
- // MIME types for common extensions.
- var extensionTypes = {
- 'css': 'text/css',
- 'html': 'text/html',
- 'htm': 'text/html',
- 'jpg': 'image/jpeg',
- 'jpeg': 'image/jpeg',
- 'js': 'text/javascript',
- 'png': 'image/png',
- 'svg': 'image/svg+xml',
- 'txt': 'text/plain'};
-
- /**
- * Constructs an HttpRequest object which tracks all of the request headers and
- * socket for an active Http request.
- * @param {Object} headers The HTTP request headers.
- * @param {Object} pSocket The socket to use for the response.
- * @constructor
- */
- function HttpRequest(headers, pSocket) {
- this.version = 'HTTP/1.1';
- this.headers = headers;
- this.responseHeaders_ = {};
- this.headersSent = false;
- this.pSocket_ = pSocket;
- this.writes_ = 0;
- this.bytesRemaining = 0;
- this.finished_ = false;
- this.readyState = 1;
- }
-
- HttpRequest.prototype = {
- __proto__: EventSource.prototype,
-
- /**
- * Closes the Http request.
- */
- close: function() {
- // The socket for keep alive connections will be re-used by the server.
- // Just stop referencing or using the socket in this HttpRequest.
- if (this.headers['Connection'] != 'keep-alive')
- pSocket.close();
-
- this.pSocket_ = null;
- this.readyState = 3;
- },
-
- /**
- * Write the provided headers as a response to the request.
- * @param {int} responseCode The HTTP status code to respond with.
- * @param {Object} responseHeaders The response headers describing the
- * response.
- */
- writeHead: function(responseCode, responseHeaders) {
- var headerString = this.version + ' ' + responseCode + ' ' +
- (responseMap[responseCode] || 'Unknown');
- this.responseHeaders_ = responseHeaders;
- if (this.headers['Connection'] == 'keep-alive')
- responseHeaders['Connection'] = 'keep-alive';
- if (!responseHeaders['Content-Length'] && responseHeaders['Connection'] == 'keep-alive')
- responseHeaders['Transfer-Encoding'] = 'chunked';
- for (var i in responseHeaders) {
- headerString += '\r\n' + i + ': ' + responseHeaders[i];
- }
- headerString += '\r\n\r\n';
- this.write_(stringToArrayBuffer(headerString));
- },
-
- /**
- * Writes data to the response stream.
- * @param {string|ArrayBuffer} data The data to write to the stream.
- */
- write: function(data) {
- if (this.responseHeaders_['Transfer-Encoding'] == 'chunked') {
- var newline = '\r\n';
- var byteLength = (data instanceof ArrayBuffer) ? data.byteLength : data.length;
- var chunkLength = byteLength.toString(16).toUpperCase() + newline;
- var buffer = new ArrayBuffer(chunkLength.length + byteLength + newline.length);
- var bufferView = new Uint8Array(buffer);
- for (var i = 0; i < chunkLength.length; i++)
- bufferView[i] = chunkLength.charCodeAt(i);
- if (data instanceof ArrayBuffer) {
- bufferView.set(new Uint8Array(data), chunkLength.length);
- } else {
- for (var i = 0; i < data.length; i++)
- bufferView[chunkLength.length + i] = data.charCodeAt(i);
- }
- for (var i = 0; i < newline.length; i++)
- bufferView[chunkLength.length + byteLength + i] = newline.charCodeAt(i);
- data = buffer;
- } else if (!(data instanceof ArrayBuffer)) {
- data = stringToArrayBuffer(data);
- }
- this.write_(data);
- },
-
- /**
- * Finishes the HTTP response writing |data| before closing.
- * @param {string|ArrayBuffer=} opt_data Optional data to write to the stream
- * before closing it.
- */
- end: function(opt_data) {
- if (opt_data)
- this.write(opt_data);
- if (this.responseHeaders_['Transfer-Encoding'] == 'chunked')
- this.write('');
- this.finished_ = true;
- this.checkFinished_();
- },
-
- /**
- * Automatically serve the given |url| request.
- * @param {string} url The URL to fetch the file to be served from. This is
- * retrieved via an XmlHttpRequest and served as the response to the
- * request.
- */
- serveUrl: function(url) {
- var t = this;
- var xhr = new XMLHttpRequest();
- xhr.onloadend = function() {
- var type = 'text/plain';
- if (this.getResponseHeader('Content-Type')) {
- type = this.getResponseHeader('Content-Type');
- } else if (url.indexOf('.') != -1) {
- var extension = url.substr(url.indexOf('.') + 1);
- type = extensionTypes[extension] || type;
- }
- console.log('Served ' + url);
- var contentLength = this.getResponseHeader('Content-Length');
- if (xhr.status == 200)
- contentLength = (this.response && this.response.byteLength) || 0;
- t.writeHead(this.status, {
- 'Content-Type': type,
- 'Content-Length': contentLength});
- t.end(this.response);
- };
- xhr.open('GET', url, true);
- xhr.responseType = 'arraybuffer';
- xhr.send();
- },
-
- write_: function(array) {
- var t = this;
- this.bytesRemaining += array.byteLength;
- this.pSocket_.write(array).then(function(bytesWritten) {
- t.bytesRemaining -= bytesWritten;
- t.checkFinished_();
- }).catch(function(e) {
- console.error(e.message);
- return;
- });
- },
-
- checkFinished_: function() {
- if (!this.finished_ || this.bytesRemaining > 0)
- return;
- this.close();
- }
- };
-
- /**
- * Constructs a server which is capable of accepting WebSocket connections.
- * @param {HttpServer} httpServer The Http Server to listen and handle
- * WebSocket upgrade requests on.
- * @constructor
- */
- function WebSocketServer(httpServer) {
- EventSource.apply(this);
- httpServer.addEventListener('upgrade', this.upgradeToWebSocket_.bind(this));
- }
-
- WebSocketServer.prototype = {
- __proto__: EventSource.prototype,
-
- upgradeToWebSocket_: function(request) {
- if (request.headers['Upgrade'] != 'websocket' ||
- !request.headers['Sec-WebSocket-Key']) {
- return false;
- }
-
- if (this.dispatchEvent('request', new WebSocketRequest(request))) {
- if (request.pSocket_)
- request.reject();
- return true;
- }
-
- return false;
- }
- };
-
- /**
- * Constructs a WebSocket request object from an Http request. This invalidates
- * the Http request's socket and offers accept and reject methods for accepting
- * and rejecting the WebSocket upgrade request.
- * @param {HttpRequest} httpRequest The HTTP request to upgrade.
- */
- function WebSocketRequest(httpRequest) {
- // We'll assume control of the socket for this request.
- HttpRequest.apply(this, [httpRequest.headers, httpRequest.pSocket_]);
- httpRequest.pSocket_ = null;
- }
-
- WebSocketRequest.prototype = {
- __proto__: HttpRequest.prototype,
-
- /**
- * Accepts the WebSocket request.
- * @return {WebSocketServerSocket} The websocket for the accepted request.
- */
- accept: function() {
- // Construct WebSocket response key.
- var clientKey = this.headers['Sec-WebSocket-Key'];
- var toArray = function(str) {
- var a = [];
- for (var i = 0; i < str.length; i++) {
- a.push(str.charCodeAt(i));
- }
- return a;
- }
- var toString = function(a) {
- var str = '';
- for (var i = 0; i < a.length; i++) {
- str += String.fromCharCode(a[i]);
- }
- return str;
- }
-
- // Magic string used for websocket connection key hashing:
- // http://en.wikipedia.org/wiki/WebSocket
- var magicStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
-
- // clientKey is base64 encoded key.
- clientKey += magicStr;
- var sha1 = new Sha1();
- sha1.reset();
- sha1.update(toArray(clientKey));
- var responseKey = btoa(toString(sha1.digest()));
- var responseHeader = {
- 'Upgrade': 'websocket',
- 'Connection': 'Upgrade',
- 'Sec-WebSocket-Accept': responseKey};
- if (this.headers['Sec-WebSocket-Protocol'])
- responseHeader['Sec-WebSocket-Protocol'] = this.headers['Sec-WebSocket-Protocol'];
- this.writeHead(101, responseHeader);
- var socket = new WebSocketServerSocket(this.pSocket_);
- // Detach the socket so that we don't use it anymore.
- this.pSocket_ = 0;
- return socket;
- },
-
- /**
- * Rejects the WebSocket request, closing the connection.
- */
- reject: function() {
- this.close();
- }
- }
-
- /**
- * Constructs a WebSocketServerSocket using the given socketId. This should be
- * a socket which has already been upgraded from an Http request.
- * @param {number} socketId The socket id with an active websocket connection.
- */
- function WebSocketServerSocket(pSocket) {
- this.pSocket_ = pSocket;
- this.readyState = 1;
- EventSource.apply(this);
- this.readFromSocket_();
- }
-
- WebSocketServerSocket.prototype = {
- __proto__: EventSource.prototype,
-
- /**
- * Send |data| on the WebSocket.
- * @param {string|Array.<number>|ArrayBuffer} data The data to send over the WebSocket.
- */
- send: function(data) {
- // WebSocket must specify opcode when send frame.
- // The opcode for data frame is 1(text) or 2(binary).
- if (typeof data == 'string' || data instanceof String) {
- this.sendFrame_(1, data);
- } else {
- this.sendFrame_(2, data);
- }
- },
-
- /**
- * Begin closing the WebSocket. Note that the WebSocket protocol uses a
- * handshake to close the connection, so this call will begin the closing
- * process.
- */
- close: function() {
- if (this.readyState === 1) {
- this.sendFrame_(8);
- this.readyState = 2;
- }
- },
-
- readFromSocket_: function() {
- var t = this;
- var data = [];
- var message = '';
- var fragmentedOp = 0;
- var fragmentedMessages = [];
-
- var onDataRead = function(dataBuffer) {
- var a = new Uint8Array(dataBuffer);
- for (var i = 0; i < a.length; i++)
- data.push(a[i]);
-
- while (data.length) {
- var length_code = -1;
- var data_start = 6;
- var mask;
- var fin = (data[0] & 128) >> 7;
- var op = data[0] & 15;
-
- if (data.length > 1)
- length_code = data[1] & 127;
- if (length_code > 125) {
- if ((length_code == 126 && data.length > 7) ||
- (length_code == 127 && data.length > 14)) {
- if (length_code == 126) {
- length_code = data[2] * 256 + data[3];
- mask = data.slice(4, 8);
- data_start = 8;
- } else if (length_code == 127) {
- length_code = 0;
- for (var i = 0; i < 8; i++) {
- length_code = length_code * 256 + data[2 + i];
- }
- mask = data.slice(10, 14);
- data_start = 14;
- }
- } else {
- length_code = -1; // Insufficient data to compute length
- }
- } else {
- if (data.length > 5)
- mask = data.slice(2, 6);
- }
-
- if (length_code > -1 && data.length >= data_start + length_code) {
- var decoded = data.slice(data_start, data_start + length_code).map(function(byte, index) {
- return byte ^ mask[index % 4];
- });
- if (op == 1) {
- decoded = ary2utf8(decoded);
- }
- data = data.slice(data_start + length_code);
- if (fin && op > 0) {
- // Unfragmented message.
- if (!t.onFrame_(op, decoded))
- return;
- } else {
- // Fragmented message.
- fragmentedOp = fragmentedOp || op;
- fragmentedMessages.push(decoded);
- if (fin) {
- var joinMessage = null;
- if (op == 1) {
- joinMessage = fragmentedMessagess.join('');
- } else {
- joinMessage = fragmentedMessages.reduce(function(pre, cur) {
- return Array.prototype.push.apply(pre, cur);
- }, []);
- }
- if (!t.onFrame_(fragmentedOp, joinMessage))
- return;
- fragmentedOp = 0;
- fragmentedMessages = [];
- }
- }
- } else {
- break; // Insufficient data, wait for more.
- }
- }
-
- return t.pSocket_.read().then(onDataRead);
- };
-
- this.pSocket_.read().then(function(data) {
- return onDataRead(data);
- }).catch(function(e) {
- t.close_();
- });
- },
-
- onFrame_: function(op, data) {
- if (op == 1 || op == 2) {
- if (typeof data == 'string' || data instanceof String) {
- // Don't do anything.
- } else if (Array.isArray(data)) {
- data = new Uint8Array(data).buffer;
- } else if (data instanceof ArrayBuffer) {
- // Don't do anything.
- } else {
- data = data.buffer;
- }
- this.dispatchEvent('message', {'data': data});
- } else if (op == 8) {
- // A close message must be confirmed before the websocket is closed.
- if (this.readyState === 1) {
- this.sendFrame_(8);
- this.readyState = 2;
- } else {
- this.close_();
- return false;
- }
- }
- return true;
- },
-
- sendFrame_: function(op, data) {
- var t = this;
- var WebsocketFrameData = function(op, data) {
- var ary = data;
- if (typeof data == 'string' || data instanceof String) {
- ary = utf82ary(data);
- }
- if (Array.isArray(ary)) {
- ary = new Uint8Array(ary);
- }
- if (ary instanceof ArrayBuffer) {
- ary = new Uint8Array(ary);
- }
- ary = new Uint8Array(ary.buffer);
- var length = ary.length;
- if (ary.length > 65535)
- length += 10;
- else if (ary.length > 125)
- length += 4;
- else
- length += 2;
- var lengthBytes = 0;
- var buffer = new ArrayBuffer(length);
- var bv = new Uint8Array(buffer);
- bv[0] = 128 | (op & 15); // Fin and type text.
- bv[1] = ary.length > 65535 ? 127 :
- (ary.length > 125 ? 126 : ary.length);
- if (ary.length > 65535)
- lengthBytes = 8;
- else if (ary.length > 125)
- lengthBytes = 2;
- var len = ary.length;
- for (var i = lengthBytes - 1; i >= 0; i--) {
- bv[2 + i] = len & 255;
- len = len >> 8;
- }
- var dataStart = lengthBytes + 2;
- for (var i = 0; i < ary.length; i++) {
- bv[dataStart + i] = ary[i];
- }
- return buffer;
- }
- var array = WebsocketFrameData(op, data || '');
- this.pSocket_.write(array).then(function(bytesWritten) {
- if (bytesWritten !== array.byteLength)
- throw new Error('insufficient write');
- }).catch(function(e) {
- t.close_();
- });
- },
-
- close_: function() {
- if (this.readyState !== 3) {
- this.pSocket_.close();
- this.readyState = 3;
- this.dispatchEvent('close');
- }
- }
- };
-
- return {
- 'Server': HttpServer,
- 'WebSocketServer': WebSocketServer,
- };
- }();
|