完成签约之后直接添加人员,录入下发凭证
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

894 lines
26 KiB

  1. /**
  2. * Copyright (c) 2013 The Chromium Authors. All rights reserved.
  3. * Use of this source code is governed by a BSD-style license that can be
  4. * found in the LICENSE file.
  5. **/
  6. var http = function() {
  7. if (!chrome.sockets || !chrome.sockets.tcpServer)
  8. return {};
  9. // Wrap chrome.sockets.tcp socketId with a Promise API.
  10. var PSocket = (function() {
  11. // chrome.sockets.tcp uses a global listener for incoming data so
  12. // use a map to dispatch to the proper instance.
  13. var socketMap = {};
  14. chrome.sockets.tcp.onReceive.addListener(function(info) {
  15. var pSocket = socketMap[info.socketId];
  16. if (pSocket) {
  17. if (pSocket.handlers) {
  18. // Fulfil the pending read.
  19. pSocket.handlers.resolve(info.data);
  20. delete pSocket.handlers;
  21. }
  22. else {
  23. // No pending read so put data on the queue.
  24. pSocket.readQueue.push(info);
  25. }
  26. }
  27. });
  28. // Read errors also use a global listener.
  29. chrome.sockets.tcp.onReceiveError.addListener(function(info) {
  30. var pSocket = socketMap[info.socketId];
  31. if (pSocket) {
  32. if (pSocket.handlers) {
  33. // Reject the pending read.
  34. pSocket.handlers.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
  35. delete pSocket.handlers;
  36. }
  37. else {
  38. // No pending read so put data on the queue.
  39. pSocket.readQueue.push(info);
  40. }
  41. }
  42. });
  43. // PSocket constructor.
  44. return function(socketId) {
  45. this.socketId = socketId;
  46. this.readQueue = [];
  47. // Register this instance for incoming data processing.
  48. socketMap[socketId] = this;
  49. chrome.sockets.tcp.setPaused(socketId, false);
  50. };
  51. })();
  52. // Returns a Promise<ArrayBuffer> with read data.
  53. PSocket.prototype.read = function() {
  54. var that = this;
  55. if (this.readQueue.length) {
  56. // Return data from the queue.
  57. var info = this.readQueue.shift();
  58. if (!info.resultCode)
  59. return Promise.resolve(info.data);
  60. else
  61. return Promise.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
  62. }
  63. else {
  64. // The queue is empty so install handlers.
  65. return new Promise(function(resolve, reject) {
  66. that.handlers = { resolve: resolve, reject: reject };
  67. });
  68. }
  69. };
  70. // Returns a Promise<integer> with the number of bytes written.
  71. PSocket.prototype.write = function(data) {
  72. var that = this;
  73. return new Promise(function(resolve, reject) {
  74. chrome.sockets.tcp.send(that.socketId, data, function(info) {
  75. if (info && info.resultCode >= 0)
  76. resolve(info.bytesSent);
  77. else
  78. reject(new Error('chrome sockets.tcp error ' + (info && info.resultCode)));
  79. });
  80. });
  81. };
  82. // Returns a Promise.
  83. PSocket.prototype.close = function() {
  84. var that = this;
  85. return new Promise(function(resolve, reject) {
  86. chrome.sockets.tcp.disconnect(that.socketId, function() {
  87. chrome.sockets.tcp.close(that.socketId, resolve);
  88. });
  89. });
  90. };
  91. // Http response code strings.
  92. var responseMap = {
  93. 200: 'OK',
  94. 301: 'Moved Permanently',
  95. 304: 'Not Modified',
  96. 400: 'Bad Request',
  97. 401: 'Unauthorized',
  98. 403: 'Forbidden',
  99. 404: 'Not Found',
  100. 413: 'Request Entity Too Large',
  101. 414: 'Request-URI Too Long',
  102. 500: 'Internal Server Error'};
  103. /**
  104. * Convert from an ArrayBuffer to a string.
  105. * @param {ArrayBuffer} buffer The array buffer to convert.
  106. * @return {string} The textual representation of the array.
  107. */
  108. var arrayBufferToString = function(buffer) {
  109. var array = new Uint8Array(buffer);
  110. var str = '';
  111. for (var i = 0; i < array.length; ++i) {
  112. str += String.fromCharCode(array[i]);
  113. }
  114. return str;
  115. };
  116. /**
  117. * Convert from an UTF-8 array to UTF-8 string.
  118. * @param {array} UTF-8 array
  119. * @return {string} UTF-8 string
  120. */
  121. var ary2utf8 = (function() {
  122. var patterns = [
  123. {pattern: '0xxxxxxx', bytes: 1},
  124. {pattern: '110xxxxx', bytes: 2},
  125. {pattern: '1110xxxx', bytes: 3},
  126. {pattern: '11110xxx', bytes: 4},
  127. {pattern: '111110xx', bytes: 5},
  128. {pattern: '1111110x', bytes: 6}
  129. ];
  130. patterns.forEach(function(item) {
  131. item.header = item.pattern.replace(/[^10]/g, '');
  132. item.pattern01 = item.pattern.replace(/[^10]/g, '0');
  133. item.pattern01 = parseInt(item.pattern01, 2);
  134. item.mask_length = item.header.length;
  135. item.data_length = 8 - item.header.length;
  136. var mask = '';
  137. for (var i = 0, len = item.mask_length; i < len; i++) {
  138. mask += '1';
  139. }
  140. for (var i = 0, len = item.data_length; i < len; i++) {
  141. mask += '0';
  142. }
  143. item.mask = mask;
  144. item.mask = parseInt(item.mask, 2);
  145. });
  146. return function(ary) {
  147. var codes = [];
  148. var cur = 0;
  149. while(cur < ary.length) {
  150. var first = ary[cur];
  151. var pattern = null;
  152. for (var i = 0, len = patterns.length; i < len; i++) {
  153. if ((first & patterns[i].mask) == patterns[i].pattern01) {
  154. pattern = patterns[i];
  155. break;
  156. }
  157. }
  158. if (pattern == null) {
  159. throw 'utf-8 decode error';
  160. }
  161. var rest = ary.slice(cur + 1, cur + pattern.bytes);
  162. cur += pattern.bytes;
  163. var code = '';
  164. code += ('00000000' + (first & (255 ^ pattern.mask)).toString(2)).slice(-pattern.data_length);
  165. for (var i = 0, len = rest.length; i < len; i++) {
  166. code += ('00000000' + (rest[i] & parseInt('111111', 2)).toString(2)).slice(-6);
  167. }
  168. codes.push(parseInt(code, 2));
  169. }
  170. return String.fromCharCode.apply(null, codes);
  171. };
  172. })();
  173. /**
  174. * Convert from an UTF-8 string to UTF-8 array.
  175. * @param {string} UTF-8 string
  176. * @return {array} UTF-8 array
  177. */
  178. var utf82ary = (function() {
  179. var patterns = [
  180. {pattern: '0xxxxxxx', bytes: 1},
  181. {pattern: '110xxxxx', bytes: 2},
  182. {pattern: '1110xxxx', bytes: 3},
  183. {pattern: '11110xxx', bytes: 4},
  184. {pattern: '111110xx', bytes: 5},
  185. {pattern: '1111110x', bytes: 6}
  186. ];
  187. patterns.forEach(function(item) {
  188. item.header = item.pattern.replace(/[^10]/g, '');
  189. item.mask_length = item.header.length;
  190. item.data_length = 8 - item.header.length;
  191. item.max_bit_length = (item.bytes - 1) * 6 + item.data_length;
  192. });
  193. var code2utf8array = function(code) {
  194. var pattern = null;
  195. var code01 = code.toString(2);
  196. for (var i = 0, len = patterns.length; i < len; i++) {
  197. if (code01.length <= patterns[i].max_bit_length) {
  198. pattern = patterns[i];
  199. break;
  200. }
  201. }
  202. if (pattern == null) {
  203. throw 'utf-8 encode error';
  204. }
  205. var ary = [];
  206. for (var i = 0, len = pattern.bytes - 1; i < len; i++) {
  207. ary.unshift(parseInt('10' + ('000000' + code01.slice(-6)).slice(-6), 2));
  208. code01 = code01.slice(0, -6);
  209. }
  210. ary.unshift(parseInt(pattern.header + ('00000000' + code01).slice(-pattern.data_length), 2));
  211. return ary;
  212. };
  213. return function(str) {
  214. var codes = [];
  215. for (var i = 0, len = str.length; i < len; i++) {
  216. var code = str.charCodeAt(i);
  217. Array.prototype.push.apply(codes, code2utf8array(code));
  218. }
  219. return codes;
  220. };
  221. })();
  222. /**
  223. * Convert a string to an ArrayBuffer.
  224. * @param {string} string The string to convert.
  225. * @return {ArrayBuffer} An array buffer whose bytes correspond to the string.
  226. */
  227. var stringToArrayBuffer = function(string) {
  228. var buffer = new ArrayBuffer(string.length);
  229. var bufferView = new Uint8Array(buffer);
  230. for (var i = 0; i < string.length; i++) {
  231. bufferView[i] = string.charCodeAt(i);
  232. }
  233. return buffer;
  234. };
  235. /**
  236. * An event source can dispatch events. These are dispatched to all of the
  237. * functions listening for that event type with arguments.
  238. * @constructor
  239. */
  240. function EventSource() {
  241. this.listeners_ = {};
  242. };
  243. EventSource.prototype = {
  244. /**
  245. * Add |callback| as a listener for |type| events.
  246. * @param {string} type The type of the event.
  247. * @param {function(Object|undefined): boolean} callback The function to call
  248. * when this event type is dispatched. Arguments depend on the event
  249. * source and type. The function returns whether the event was "handled"
  250. * which will prevent delivery to the rest of the listeners.
  251. */
  252. addEventListener: function(type, callback) {
  253. if (!this.listeners_[type])
  254. this.listeners_[type] = [];
  255. this.listeners_[type].push(callback);
  256. },
  257. /**
  258. * Remove |callback| as a listener for |type| events.
  259. * @param {string} type The type of the event.
  260. * @param {function(Object|undefined): boolean} callback The callback
  261. * function to remove from the event listeners for events having type
  262. * |type|.
  263. */
  264. removeEventListener: function(type, callback) {
  265. if (!this.listeners_[type])
  266. return;
  267. for (var i = this.listeners_[type].length - 1; i >= 0; i--) {
  268. if (this.listeners_[type][i] == callback) {
  269. this.listeners_[type].splice(i, 1);
  270. }
  271. }
  272. },
  273. /**
  274. * Dispatch an event to all listeners for events of type |type|.
  275. * @param {type} type The type of the event being dispatched.
  276. * @param {...Object} var_args The arguments to pass when calling the
  277. * callback function.
  278. * @return {boolean} Returns true if the event was handled.
  279. */
  280. dispatchEvent: function(type, var_args) {
  281. if (!this.listeners_[type])
  282. return false;
  283. for (var i = 0; i < this.listeners_[type].length; i++) {
  284. if (this.listeners_[type][i].apply(
  285. /* this */ null,
  286. /* var_args */ Array.prototype.slice.call(arguments, 1))) {
  287. return true;
  288. }
  289. }
  290. }
  291. };
  292. /**
  293. * HttpServer provides a lightweight Http web server. Currently it only
  294. * supports GET requests and upgrading to other protocols (i.e. WebSockets).
  295. * @constructor
  296. */
  297. function HttpServer() {
  298. EventSource.apply(this);
  299. this.readyState_ = 0;
  300. }
  301. HttpServer.prototype = {
  302. __proto__: EventSource.prototype,
  303. /**
  304. * Listen for connections on |port| using the interface |host|.
  305. * @param {number} port The port to listen for incoming connections on.
  306. * @param {string=} opt_host The host interface to listen for connections on.
  307. * This will default to 0.0.0.0 if not specified which will listen on
  308. * all interfaces.
  309. */
  310. listen: function(port, opt_host) {
  311. var t = this;
  312. chrome.sockets.tcpServer.create(function(socketInfo) {
  313. chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) {
  314. if (acceptInfo.socketId === socketInfo.socketId)
  315. t.readRequestFromSocket_(new PSocket(acceptInfo.clientSocketId));
  316. });
  317. this.socketId = socketInfo.socketId;
  318. chrome.runtime.sendMessage({socketId: this.socketId});
  319. chrome.sockets.tcpServer.listen(
  320. socketInfo.socketId,
  321. opt_host || '0.0.0.0',
  322. port,
  323. 50,
  324. function(result) {
  325. if (!result) {
  326. t.readyState_ = 1;
  327. }
  328. else {
  329. console.log(
  330. 'listen error ' +
  331. chrome.runtime.lastError.message +
  332. ' (normal if another instance is already serving requests)');
  333. }
  334. });
  335. });
  336. },
  337. close: function() {
  338. if (this.socketId) {
  339. chrome.sockets.tcpServer.close(this.socketId);
  340. this.socketId = 0;
  341. }
  342. },
  343. readRequestFromSocket_: function(pSocket) {
  344. var t = this;
  345. var requestData = '';
  346. var endIndex = 0;
  347. var onDataRead = function(data) {
  348. requestData += arrayBufferToString(data).replace(/\r\n/g, '\n');
  349. // Check for end of request.
  350. endIndex = requestData.indexOf('\n\n', endIndex);
  351. if (endIndex == -1) {
  352. endIndex = requestData.length - 1;
  353. return pSocket.read().then(onDataRead);
  354. }
  355. var headers = requestData.substring(0, endIndex).split('\n');
  356. var headerMap = {};
  357. // headers[0] should be the Request-Line
  358. var requestLine = headers[0].split(' ');
  359. headerMap['method'] = requestLine[0];
  360. headerMap['url'] = requestLine[1];
  361. headerMap['Http-Version'] = requestLine[2];
  362. for (var i = 1; i < headers.length; i++) {
  363. requestLine = headers[i].split(':', 2);
  364. if (requestLine.length == 2)
  365. headerMap[requestLine[0]] = requestLine[1].trim();
  366. }
  367. var request = new HttpRequest(headerMap, pSocket);
  368. t.onRequest_(request);
  369. };
  370. pSocket.read().then(onDataRead).catch(function(e) {
  371. pSocket.close();
  372. });
  373. },
  374. onRequest_: function(request) {
  375. var type = request.headers['Upgrade'] ? 'upgrade' : 'request';
  376. var keepAlive = request.headers['Connection'] == 'keep-alive';
  377. if (!this.dispatchEvent(type, request))
  378. request.close();
  379. else if (keepAlive)
  380. this.readRequestFromSocket_(request.pSocket_);
  381. },
  382. };
  383. // MIME types for common extensions.
  384. var extensionTypes = {
  385. 'css': 'text/css',
  386. 'html': 'text/html',
  387. 'htm': 'text/html',
  388. 'jpg': 'image/jpeg',
  389. 'jpeg': 'image/jpeg',
  390. 'js': 'text/javascript',
  391. 'png': 'image/png',
  392. 'svg': 'image/svg+xml',
  393. 'txt': 'text/plain'};
  394. /**
  395. * Constructs an HttpRequest object which tracks all of the request headers and
  396. * socket for an active Http request.
  397. * @param {Object} headers The HTTP request headers.
  398. * @param {Object} pSocket The socket to use for the response.
  399. * @constructor
  400. */
  401. function HttpRequest(headers, pSocket) {
  402. this.version = 'HTTP/1.1';
  403. this.headers = headers;
  404. this.responseHeaders_ = {};
  405. this.headersSent = false;
  406. this.pSocket_ = pSocket;
  407. this.writes_ = 0;
  408. this.bytesRemaining = 0;
  409. this.finished_ = false;
  410. this.readyState = 1;
  411. }
  412. HttpRequest.prototype = {
  413. __proto__: EventSource.prototype,
  414. /**
  415. * Closes the Http request.
  416. */
  417. close: function() {
  418. // The socket for keep alive connections will be re-used by the server.
  419. // Just stop referencing or using the socket in this HttpRequest.
  420. if (this.headers['Connection'] != 'keep-alive')
  421. pSocket.close();
  422. this.pSocket_ = null;
  423. this.readyState = 3;
  424. },
  425. /**
  426. * Write the provided headers as a response to the request.
  427. * @param {int} responseCode The HTTP status code to respond with.
  428. * @param {Object} responseHeaders The response headers describing the
  429. * response.
  430. */
  431. writeHead: function(responseCode, responseHeaders) {
  432. var headerString = this.version + ' ' + responseCode + ' ' +
  433. (responseMap[responseCode] || 'Unknown');
  434. this.responseHeaders_ = responseHeaders;
  435. if (this.headers['Connection'] == 'keep-alive')
  436. responseHeaders['Connection'] = 'keep-alive';
  437. if (!responseHeaders['Content-Length'] && responseHeaders['Connection'] == 'keep-alive')
  438. responseHeaders['Transfer-Encoding'] = 'chunked';
  439. for (var i in responseHeaders) {
  440. headerString += '\r\n' + i + ': ' + responseHeaders[i];
  441. }
  442. headerString += '\r\n\r\n';
  443. this.write_(stringToArrayBuffer(headerString));
  444. },
  445. /**
  446. * Writes data to the response stream.
  447. * @param {string|ArrayBuffer} data The data to write to the stream.
  448. */
  449. write: function(data) {
  450. if (this.responseHeaders_['Transfer-Encoding'] == 'chunked') {
  451. var newline = '\r\n';
  452. var byteLength = (data instanceof ArrayBuffer) ? data.byteLength : data.length;
  453. var chunkLength = byteLength.toString(16).toUpperCase() + newline;
  454. var buffer = new ArrayBuffer(chunkLength.length + byteLength + newline.length);
  455. var bufferView = new Uint8Array(buffer);
  456. for (var i = 0; i < chunkLength.length; i++)
  457. bufferView[i] = chunkLength.charCodeAt(i);
  458. if (data instanceof ArrayBuffer) {
  459. bufferView.set(new Uint8Array(data), chunkLength.length);
  460. } else {
  461. for (var i = 0; i < data.length; i++)
  462. bufferView[chunkLength.length + i] = data.charCodeAt(i);
  463. }
  464. for (var i = 0; i < newline.length; i++)
  465. bufferView[chunkLength.length + byteLength + i] = newline.charCodeAt(i);
  466. data = buffer;
  467. } else if (!(data instanceof ArrayBuffer)) {
  468. data = stringToArrayBuffer(data);
  469. }
  470. this.write_(data);
  471. },
  472. /**
  473. * Finishes the HTTP response writing |data| before closing.
  474. * @param {string|ArrayBuffer=} opt_data Optional data to write to the stream
  475. * before closing it.
  476. */
  477. end: function(opt_data) {
  478. if (opt_data)
  479. this.write(opt_data);
  480. if (this.responseHeaders_['Transfer-Encoding'] == 'chunked')
  481. this.write('');
  482. this.finished_ = true;
  483. this.checkFinished_();
  484. },
  485. /**
  486. * Automatically serve the given |url| request.
  487. * @param {string} url The URL to fetch the file to be served from. This is
  488. * retrieved via an XmlHttpRequest and served as the response to the
  489. * request.
  490. */
  491. serveUrl: function(url) {
  492. var t = this;
  493. var xhr = new XMLHttpRequest();
  494. xhr.onloadend = function() {
  495. var type = 'text/plain';
  496. if (this.getResponseHeader('Content-Type')) {
  497. type = this.getResponseHeader('Content-Type');
  498. } else if (url.indexOf('.') != -1) {
  499. var extension = url.substr(url.indexOf('.') + 1);
  500. type = extensionTypes[extension] || type;
  501. }
  502. console.log('Served ' + url);
  503. var contentLength = this.getResponseHeader('Content-Length');
  504. if (xhr.status == 200)
  505. contentLength = (this.response && this.response.byteLength) || 0;
  506. t.writeHead(this.status, {
  507. 'Content-Type': type,
  508. 'Content-Length': contentLength});
  509. t.end(this.response);
  510. };
  511. xhr.open('GET', url, true);
  512. xhr.responseType = 'arraybuffer';
  513. xhr.send();
  514. },
  515. write_: function(array) {
  516. var t = this;
  517. this.bytesRemaining += array.byteLength;
  518. this.pSocket_.write(array).then(function(bytesWritten) {
  519. t.bytesRemaining -= bytesWritten;
  520. t.checkFinished_();
  521. }).catch(function(e) {
  522. console.error(e.message);
  523. return;
  524. });
  525. },
  526. checkFinished_: function() {
  527. if (!this.finished_ || this.bytesRemaining > 0)
  528. return;
  529. this.close();
  530. }
  531. };
  532. /**
  533. * Constructs a server which is capable of accepting WebSocket connections.
  534. * @param {HttpServer} httpServer The Http Server to listen and handle
  535. * WebSocket upgrade requests on.
  536. * @constructor
  537. */
  538. function WebSocketServer(httpServer) {
  539. EventSource.apply(this);
  540. httpServer.addEventListener('upgrade', this.upgradeToWebSocket_.bind(this));
  541. }
  542. WebSocketServer.prototype = {
  543. __proto__: EventSource.prototype,
  544. upgradeToWebSocket_: function(request) {
  545. if (request.headers['Upgrade'] != 'websocket' ||
  546. !request.headers['Sec-WebSocket-Key']) {
  547. return false;
  548. }
  549. if (this.dispatchEvent('request', new WebSocketRequest(request))) {
  550. if (request.pSocket_)
  551. request.reject();
  552. return true;
  553. }
  554. return false;
  555. }
  556. };
  557. /**
  558. * Constructs a WebSocket request object from an Http request. This invalidates
  559. * the Http request's socket and offers accept and reject methods for accepting
  560. * and rejecting the WebSocket upgrade request.
  561. * @param {HttpRequest} httpRequest The HTTP request to upgrade.
  562. */
  563. function WebSocketRequest(httpRequest) {
  564. // We'll assume control of the socket for this request.
  565. HttpRequest.apply(this, [httpRequest.headers, httpRequest.pSocket_]);
  566. httpRequest.pSocket_ = null;
  567. }
  568. WebSocketRequest.prototype = {
  569. __proto__: HttpRequest.prototype,
  570. /**
  571. * Accepts the WebSocket request.
  572. * @return {WebSocketServerSocket} The websocket for the accepted request.
  573. */
  574. accept: function() {
  575. // Construct WebSocket response key.
  576. var clientKey = this.headers['Sec-WebSocket-Key'];
  577. var toArray = function(str) {
  578. var a = [];
  579. for (var i = 0; i < str.length; i++) {
  580. a.push(str.charCodeAt(i));
  581. }
  582. return a;
  583. }
  584. var toString = function(a) {
  585. var str = '';
  586. for (var i = 0; i < a.length; i++) {
  587. str += String.fromCharCode(a[i]);
  588. }
  589. return str;
  590. }
  591. // Magic string used for websocket connection key hashing:
  592. // http://en.wikipedia.org/wiki/WebSocket
  593. var magicStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  594. // clientKey is base64 encoded key.
  595. clientKey += magicStr;
  596. var sha1 = new Sha1();
  597. sha1.reset();
  598. sha1.update(toArray(clientKey));
  599. var responseKey = btoa(toString(sha1.digest()));
  600. var responseHeader = {
  601. 'Upgrade': 'websocket',
  602. 'Connection': 'Upgrade',
  603. 'Sec-WebSocket-Accept': responseKey};
  604. if (this.headers['Sec-WebSocket-Protocol'])
  605. responseHeader['Sec-WebSocket-Protocol'] = this.headers['Sec-WebSocket-Protocol'];
  606. this.writeHead(101, responseHeader);
  607. var socket = new WebSocketServerSocket(this.pSocket_);
  608. // Detach the socket so that we don't use it anymore.
  609. this.pSocket_ = 0;
  610. return socket;
  611. },
  612. /**
  613. * Rejects the WebSocket request, closing the connection.
  614. */
  615. reject: function() {
  616. this.close();
  617. }
  618. }
  619. /**
  620. * Constructs a WebSocketServerSocket using the given socketId. This should be
  621. * a socket which has already been upgraded from an Http request.
  622. * @param {number} socketId The socket id with an active websocket connection.
  623. */
  624. function WebSocketServerSocket(pSocket) {
  625. this.pSocket_ = pSocket;
  626. this.readyState = 1;
  627. EventSource.apply(this);
  628. this.readFromSocket_();
  629. }
  630. WebSocketServerSocket.prototype = {
  631. __proto__: EventSource.prototype,
  632. /**
  633. * Send |data| on the WebSocket.
  634. * @param {string|Array.<number>|ArrayBuffer} data The data to send over the WebSocket.
  635. */
  636. send: function(data) {
  637. // WebSocket must specify opcode when send frame.
  638. // The opcode for data frame is 1(text) or 2(binary).
  639. if (typeof data == 'string' || data instanceof String) {
  640. this.sendFrame_(1, data);
  641. } else {
  642. this.sendFrame_(2, data);
  643. }
  644. },
  645. /**
  646. * Begin closing the WebSocket. Note that the WebSocket protocol uses a
  647. * handshake to close the connection, so this call will begin the closing
  648. * process.
  649. */
  650. close: function() {
  651. if (this.readyState === 1) {
  652. this.sendFrame_(8);
  653. this.readyState = 2;
  654. }
  655. },
  656. readFromSocket_: function() {
  657. var t = this;
  658. var data = [];
  659. var message = '';
  660. var fragmentedOp = 0;
  661. var fragmentedMessages = [];
  662. var onDataRead = function(dataBuffer) {
  663. var a = new Uint8Array(dataBuffer);
  664. for (var i = 0; i < a.length; i++)
  665. data.push(a[i]);
  666. while (data.length) {
  667. var length_code = -1;
  668. var data_start = 6;
  669. var mask;
  670. var fin = (data[0] & 128) >> 7;
  671. var op = data[0] & 15;
  672. if (data.length > 1)
  673. length_code = data[1] & 127;
  674. if (length_code > 125) {
  675. if ((length_code == 126 && data.length > 7) ||
  676. (length_code == 127 && data.length > 14)) {
  677. if (length_code == 126) {
  678. length_code = data[2] * 256 + data[3];
  679. mask = data.slice(4, 8);
  680. data_start = 8;
  681. } else if (length_code == 127) {
  682. length_code = 0;
  683. for (var i = 0; i < 8; i++) {
  684. length_code = length_code * 256 + data[2 + i];
  685. }
  686. mask = data.slice(10, 14);
  687. data_start = 14;
  688. }
  689. } else {
  690. length_code = -1; // Insufficient data to compute length
  691. }
  692. } else {
  693. if (data.length > 5)
  694. mask = data.slice(2, 6);
  695. }
  696. if (length_code > -1 && data.length >= data_start + length_code) {
  697. var decoded = data.slice(data_start, data_start + length_code).map(function(byte, index) {
  698. return byte ^ mask[index % 4];
  699. });
  700. if (op == 1) {
  701. decoded = ary2utf8(decoded);
  702. }
  703. data = data.slice(data_start + length_code);
  704. if (fin && op > 0) {
  705. // Unfragmented message.
  706. if (!t.onFrame_(op, decoded))
  707. return;
  708. } else {
  709. // Fragmented message.
  710. fragmentedOp = fragmentedOp || op;
  711. fragmentedMessages.push(decoded);
  712. if (fin) {
  713. var joinMessage = null;
  714. if (op == 1) {
  715. joinMessage = fragmentedMessagess.join('');
  716. } else {
  717. joinMessage = fragmentedMessages.reduce(function(pre, cur) {
  718. return Array.prototype.push.apply(pre, cur);
  719. }, []);
  720. }
  721. if (!t.onFrame_(fragmentedOp, joinMessage))
  722. return;
  723. fragmentedOp = 0;
  724. fragmentedMessages = [];
  725. }
  726. }
  727. } else {
  728. break; // Insufficient data, wait for more.
  729. }
  730. }
  731. return t.pSocket_.read().then(onDataRead);
  732. };
  733. this.pSocket_.read().then(function(data) {
  734. return onDataRead(data);
  735. }).catch(function(e) {
  736. t.close_();
  737. });
  738. },
  739. onFrame_: function(op, data) {
  740. if (op == 1 || op == 2) {
  741. if (typeof data == 'string' || data instanceof String) {
  742. // Don't do anything.
  743. } else if (Array.isArray(data)) {
  744. data = new Uint8Array(data).buffer;
  745. } else if (data instanceof ArrayBuffer) {
  746. // Don't do anything.
  747. } else {
  748. data = data.buffer;
  749. }
  750. this.dispatchEvent('message', {'data': data});
  751. } else if (op == 8) {
  752. // A close message must be confirmed before the websocket is closed.
  753. if (this.readyState === 1) {
  754. this.sendFrame_(8);
  755. this.readyState = 2;
  756. } else {
  757. this.close_();
  758. return false;
  759. }
  760. }
  761. return true;
  762. },
  763. sendFrame_: function(op, data) {
  764. var t = this;
  765. var WebsocketFrameData = function(op, data) {
  766. var ary = data;
  767. if (typeof data == 'string' || data instanceof String) {
  768. ary = utf82ary(data);
  769. }
  770. if (Array.isArray(ary)) {
  771. ary = new Uint8Array(ary);
  772. }
  773. if (ary instanceof ArrayBuffer) {
  774. ary = new Uint8Array(ary);
  775. }
  776. ary = new Uint8Array(ary.buffer);
  777. var length = ary.length;
  778. if (ary.length > 65535)
  779. length += 10;
  780. else if (ary.length > 125)
  781. length += 4;
  782. else
  783. length += 2;
  784. var lengthBytes = 0;
  785. var buffer = new ArrayBuffer(length);
  786. var bv = new Uint8Array(buffer);
  787. bv[0] = 128 | (op & 15); // Fin and type text.
  788. bv[1] = ary.length > 65535 ? 127 :
  789. (ary.length > 125 ? 126 : ary.length);
  790. if (ary.length > 65535)
  791. lengthBytes = 8;
  792. else if (ary.length > 125)
  793. lengthBytes = 2;
  794. var len = ary.length;
  795. for (var i = lengthBytes - 1; i >= 0; i--) {
  796. bv[2 + i] = len & 255;
  797. len = len >> 8;
  798. }
  799. var dataStart = lengthBytes + 2;
  800. for (var i = 0; i < ary.length; i++) {
  801. bv[dataStart + i] = ary[i];
  802. }
  803. return buffer;
  804. }
  805. var array = WebsocketFrameData(op, data || '');
  806. this.pSocket_.write(array).then(function(bytesWritten) {
  807. if (bytesWritten !== array.byteLength)
  808. throw new Error('insufficient write');
  809. }).catch(function(e) {
  810. t.close_();
  811. });
  812. },
  813. close_: function() {
  814. if (this.readyState !== 3) {
  815. this.pSocket_.close();
  816. this.readyState = 3;
  817. this.dispatchEvent('close');
  818. }
  819. }
  820. };
  821. return {
  822. 'Server': HttpServer,
  823. 'WebSocketServer': WebSocketServer,
  824. };
  825. }();