├── .npmrc ├── crt ├── rootCA.srl ├── v3.ext ├── server.csr.cnf ├── server.csr ├── server.crt ├── rootCA.pem ├── server.key └── rootCA.key ├── test ├── chrome │ ├── mocha-prepare.js │ ├── background.js │ ├── mocha-run.js │ ├── index.html │ ├── manifest.json │ ├── run.js │ ├── remote-debugger.js │ └── chrome-integration.js ├── constants.js ├── ws │ ├── index.html │ ├── ws-integration.js │ └── index.js ├── echo.js ├── node │ └── node-integration.js └── starttls.js ├── scripts ├── build.sh └── create-root-CA.sh ├── testutils.js ├── .babelrc ├── .editorconfig ├── .gitignore ├── src ├── worker-utils.js ├── socket.js ├── tls-worker.js ├── timeout.js ├── node-socket-unit.js ├── tls-utils.js ├── node-socket.js ├── tls-unit.js ├── socketio-socket-unit.js ├── windows-socket-unit.js ├── socketio-socket.js ├── tls.js ├── windows-socket.js ├── chrome-socket-unit.js └── chrome-socket.js ├── webpack.config.worker.js ├── .travis.yml ├── webpack.config.test.js ├── webpack.config.chrome.js ├── .vscode └── launch.json ├── LICENSE ├── wdio.conf.js ├── dist ├── worker-utils.js ├── socket.js ├── tls-worker.js ├── timeout.js ├── node-socket.js ├── socketio-socket.js ├── tls.js └── windows-socket.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /crt/rootCA.srl: -------------------------------------------------------------------------------- 1 | B9EA6EF631A1962B 2 | -------------------------------------------------------------------------------- /test/chrome/mocha-prepare.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict' 3 | window.mocha.setup('bdd') 4 | })() 5 | -------------------------------------------------------------------------------- /test/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PORT_NET: 8000, 3 | PORT_TLS: 8001, 4 | PORT_STARTTLS: 8002 5 | } 6 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm run build 4 | git reset 5 | git add dist 6 | git add res 7 | git commit -m 'Updating dist files' 8 | -------------------------------------------------------------------------------- /testutils.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | 4 | global.expect = expect 5 | global.sinon = sinon 6 | -------------------------------------------------------------------------------- /test/chrome/background.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | chrome.app.runtime.onLaunched.addListener(function () { 4 | chrome.app.window.create('index.html', { width: 1024, height: 650 }) 5 | }) 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | ["babel-plugin-inline-import", { 5 | "extensions": [ 6 | ".blob" 7 | ] 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/ws/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /crt/v3.ext: -------------------------------------------------------------------------------- 1 | authorityKeyIdentifier=keyid,issuer 2 | basicConstraints=CA:FALSE 3 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment 4 | subjectAltName = @alt_names 5 | 6 | [alt_names] 7 | DNS.1 = localhost 8 | -------------------------------------------------------------------------------- /test/chrome/mocha-run.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict' 3 | window.mocha.run(function (failureCount) { 4 | if (!failureCount) { 5 | console.log('All tests passed!') 6 | } else { 7 | console.log('Failure count: ' + failureCount) 8 | } 9 | }) 10 | })() 11 | -------------------------------------------------------------------------------- /crt/server.csr.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | default_bits = 2048 3 | prompt = no 4 | default_md = sha256 5 | distinguished_name = dn 6 | 7 | [dn] 8 | C=US 9 | ST=New York 10 | L=Rochester 11 | O=End Point 12 | OU=Testing Domain 13 | emailAddress=your-administrative-address@your-awesome-existing-domain.com 14 | CN = localhost 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | package-lock.json 5 | test/ws/error-shots 6 | test/ws/index.comp.js 7 | test/chrome/temp-data-dir 8 | test/chrome/chrome-integration.comp.js 9 | test/chrome/mocha.* 10 | test/chrome/chai.* 11 | 12 | # VIM Swap Files 13 | [._]*.s[a-v][a-z] 14 | [._]*.sw[a-p] 15 | [._]s[a-v][a-z] 16 | [._]sw[a-p] 17 | -------------------------------------------------------------------------------- /src/worker-utils.js: -------------------------------------------------------------------------------- 1 | export const EVENT_INBOUND = 'inbound' 2 | export const EVENT_OUTBOUND = 'outbound' 3 | export const EVENT_OPEN = 'open' 4 | export const EVENT_CLOSE = 'close' 5 | export const EVENT_ERROR = 'error' 6 | export const EVENT_CONFIG = 'configure' 7 | export const EVENT_CERT = 'cert' 8 | export const EVENT_HANDSHAKE = 'handshake' 9 | 10 | export const createMessage = (event, message) => ({ event, message }) 11 | -------------------------------------------------------------------------------- /webpack.config.worker.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/tls-worker.js', 5 | output: { 6 | path: path.resolve(__dirname, 'res'), 7 | filename: 'tls.worker.js' 8 | }, 9 | node: false, 10 | module: { 11 | rules: [{ 12 | exclude: /node_modules/, 13 | use: { 14 | loader: 'babel-loader', 15 | options: { 16 | presets: ['babel-preset-env'] 17 | } 18 | } 19 | }] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/chrome/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 5 | - lts/* 6 | notifications: 7 | email: 8 | recipients: 9 | - felix.hammerl@gmail.com 10 | addons: 11 | chrome: stable 12 | before_script: 13 | - export DISPLAY=:99.0 14 | - export CHROME_PATH="$(pwd)/chrome-linux/chrome" 15 | - sh -e /etc/init.d/xvfb start 16 | - sleep 3 # wait for xvfb to boot 17 | - sudo cp crt/rootCA.pem /usr/local/share/ca-certificates/ 18 | - sudo cp crt/server.crt /usr/local/share/ca-certificates/ 19 | - sudo update-ca-certificates 20 | 21 | -------------------------------------------------------------------------------- /test/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcp-socket with chrome.sockets", 3 | "description": "Example of a TCPSocket shim for Chrome Packaged Apps", 4 | "version": "0.0.1", 5 | "manifest_version": 2, 6 | "offline_enabled": false, 7 | "permissions": [ 8 | { 9 | "socket": [ 10 | "tcp-connect" 11 | ] 12 | } 13 | ], 14 | // "sockets": { 15 | // "tcp": { 16 | // "connect": "" 17 | // } 18 | // }, 19 | "app": { 20 | "background": { 21 | "scripts": [ 22 | "background.js" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/ws/ws-integration.js: -------------------------------------------------------------------------------- 1 | describe('Websocket tests', () => { 2 | it('should see plaintext data being sent and received', () => { 3 | browser.timeouts('implicit', 60000) 4 | browser.url('http://localhost:12345/') 5 | browser.waitForExist('#plaintext', 50000) 6 | expect(browser.getValue('#plaintext')).to.equal('payload') 7 | }) 8 | it('should see TLS data being sent and received', () => { 9 | browser.timeouts('implicit', 60000) 10 | browser.url('http://localhost:12345/') 11 | browser.waitForExist('#tls', 50000) 12 | expect(browser.getValue('#tls')).to.equal('payload') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /webpack.config.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './test/ws/index.js', 5 | output: { 6 | path: path.resolve(__dirname, 'test', 'ws'), 7 | filename: 'index.comp.js' 8 | }, 9 | node: { 10 | net: 'empty', 11 | tls: 'empty', 12 | Buffer: false, 13 | process: false 14 | }, 15 | devtool: 'inline-source-map', 16 | module: { 17 | rules: [{ 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'babel-loader', 21 | options: { 22 | presets: ['babel-preset-env'] 23 | } 24 | } 25 | }] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/create-root-CA.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$PWD 4 | 5 | rm -f $DIR/crt/*.key $DIR/crt/*.pem $DIR/crt/*.csr 6 | 7 | openssl genrsa -des3 -out $DIR/crt/rootCA.key 2048 8 | openssl req -x509 -new -nodes -key $DIR/crt/rootCA.key -sha256 -days 1024 -out $DIR/crt/rootCA.pem 9 | openssl req -new -sha256 -nodes -out $DIR/crt/server.csr -newkey rsa:2048 -keyout $DIR/crt/server.key -config <(cat $DIR/crt/server.csr.cnf) 10 | openssl x509 -req -in $DIR/crt/server.csr -CA $DIR/crt/rootCA.pem -CAkey $DIR/crt/rootCA.key -CAcreateserial -out $DIR/crt/server.crt -days 500 -sha256 -extfile $DIR/crt/v3.ext 11 | openssl x509 -text -in $DIR/crt/server.crt -noout 12 | -------------------------------------------------------------------------------- /webpack.config.chrome.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './test/chrome/chrome-integration.js', 5 | output: { 6 | path: path.resolve(__dirname, 'test', 'chrome'), 7 | filename: 'chrome-integration.comp.js' 8 | }, 9 | node: { 10 | net: 'empty', 11 | tls: 'empty', 12 | Buffer: false, 13 | process: false 14 | }, 15 | devtool: 'inline-source-map', 16 | module: { 17 | rules: [{ 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'babel-loader', 21 | options: { 22 | presets: ['babel-preset-env'] 23 | } 24 | } 25 | }] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run ES6 Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 10 | "stopOnEntry": false, 11 | "args": [ 12 | "./src/*-unit.js", 13 | "--require", "babel-register", 14 | "--require", "babel-polyfill", 15 | "testutils.js", 16 | "--reporter", "spec", 17 | "--no-timeouts" 18 | ], 19 | "runtimeArgs": [ 20 | "--nolazy" 21 | ], 22 | "sourceMaps": true 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/socket.js: -------------------------------------------------------------------------------- 1 | let TCPSocket 2 | 3 | class DummySocket { 4 | static open () { 5 | throw new Error('Runtime does not offer raw sockets!') 6 | } 7 | } 8 | 9 | if (typeof process !== 'undefined') { 10 | TCPSocket = require('./node-socket') 11 | } else if (typeof chrome !== 'undefined' && (chrome.socket || chrome.sockets)) { 12 | TCPSocket = require('./chrome-socket') 13 | } else if (typeof Windows === 'object' && Windows && Windows.Networking && Windows.Networking.Sockets && Windows.Networking.Sockets.StreamSocket) { 14 | TCPSocket = require('./windows-socket') 15 | } else if (typeof window === 'object' && typeof io === 'function') { 16 | TCPSocket = require('./socketio-socket') 17 | } else { 18 | TCPSocket = DummySocket 19 | } 20 | 21 | module.exports = TCPSocket 22 | -------------------------------------------------------------------------------- /test/chrome/run.js: -------------------------------------------------------------------------------- 1 | import { pathOr } from 'ramda' 2 | import { attachDebugger, setDebugHandler } from './remote-debugger.js' 3 | import { launch } from 'chrome-launcher' 4 | import echo from '../echo' 5 | 6 | const { startServers, stopServers } = echo() 7 | let chrome 8 | 9 | startServers() 10 | .then(() => launch({ port: 9222, chromeFlags: [`--load-and-launch-app=${__dirname}`], enableExtensions: true })) 11 | .then(child => { chrome = child }) 12 | .then(() => attachDebugger()) 13 | .then(() => new Promise((resolve, reject) => { 14 | setDebugHandler(data => { 15 | var message = pathOr('', ['params', 'message', 'text'])(data) 16 | if (message === 'All tests passed!') { 17 | resolve(message) 18 | } else if (/Failure count: [\d]+/.test(message)) { 19 | reject(message) 20 | } 21 | }) 22 | })) 23 | .then(msg => { 24 | console.log(msg) 25 | chrome.kill() 26 | stopServers() 27 | process.exit(0) 28 | }) 29 | .catch(e => { 30 | console.error(e) 31 | chrome.kill() 32 | stopServers() 33 | process.exit(1) 34 | }) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Whiteout Networks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/tls-worker.js: -------------------------------------------------------------------------------- 1 | import { 2 | EVENT_HANDSHAKE, 3 | EVENT_INBOUND, EVENT_OUTBOUND, 4 | EVENT_OPEN, EVENT_CLOSE, 5 | EVENT_CONFIG, EVENT_CERT, 6 | EVENT_ERROR, 7 | createMessage 8 | } from './worker-utils' 9 | import TLS from './tls' 10 | 11 | var tls = new TLS() 12 | tls.tlserror = message => self.postMessage(createMessage(EVENT_ERROR, message)) 13 | tls.tlscert = cert => self.postMessage(createMessage(EVENT_CERT, cert)) 14 | tls.tlsclose = () => self.postMessage(createMessage(EVENT_CLOSE)) 15 | tls.tlsopen = () => self.postMessage(createMessage(EVENT_OPEN)) 16 | tls.tlsoutbound = buffer => self.postMessage(createMessage(EVENT_OUTBOUND, buffer), [buffer]) 17 | tls.tlsinbound = buffer => self.postMessage(createMessage(EVENT_INBOUND, buffer), [buffer]) 18 | 19 | self.onmessage = function ({ data: { event, message } }) { 20 | switch (event) { 21 | case EVENT_INBOUND: 22 | tls.processInbound(message) 23 | break 24 | case EVENT_OUTBOUND: 25 | tls.prepareOutbound(message) 26 | break 27 | case EVENT_HANDSHAKE: 28 | tls.handshake() 29 | break 30 | case EVENT_CONFIG: 31 | tls.configure(message) 32 | break 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crt/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIDCDCCAfACAQAwgcIxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazES 3 | MBAGA1UEBwwJUm9jaGVzdGVyMRIwEAYDVQQKDAlFbmQgUG9pbnQxFzAVBgNVBAsM 4 | DlRlc3RpbmcgRG9tYWluMUswSQYJKoZIhvcNAQkBFjx5b3VyLWFkbWluaXN0cmF0 5 | aXZlLWFkZHJlc3NAeW91ci1hd2Vzb21lLWV4aXN0aW5nLWRvbWFpbi5jb20xEjAQ 6 | BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 7 | AOgqM9H1Gm+sjYXjFyukbK8I5A4m7JXdbJdTUGl6iqzMRJVaqVyTITrpevmKADmm 8 | TxuswaRdLq/+612DIZzzVxt2g9Q/30Yr793qKDAmDTymjwm4BLv7RZ0t6mndMtrX 9 | d7YifuZVDWUFDn6Wwuf83nTmWxokbQAO8TEziom84er7bDcFLgqjfYwrKMXbPJp5 10 | U5I251UmwDaihh5xDZKPZNmOhd3Vg8PJLQlZXVU5K8trYpNtQKPLw8HMhrHZiB6K 11 | UjP8G7ZAALVncKbRnMED7sg/rbqu/UV9GVjla8Ga6KTJIWtm3yJCRE3+FYM9Hjbl 12 | Jf6pOOzecFOqn2wE+HKouX0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQC3t+PQ 13 | JXnxK2Wq2n0aidCay1HPMI+KQ8SetJmWduaelLL4scGM5q6w+tBH61AhZtLN7Y1x 14 | fFIhBq2YUVhtplK3F0Fh29f2IUef+2Rtz2UfQ1yPnJXJVTGDhJ6qiEyZqw7zHYwF 15 | q4QhJP3JbQ1Vr5B5nN+V9RiL91fH8mZQ7DvcLjCtHXmGDTB/hOM1vD6wFAskYkn4 16 | RP89/0oSmxQblk/mwpORpgwIewNAf76imUzPu+zB9tySMJmA0PCtsgkbhTbVLn+Q 17 | puAPyhwSrXtnpn2lqyXvHz68cKs/HHz9cxoVFIrwkRS6V7dpifmqZGQS8F48IE7h 18 | VoqatRbKEJZe90qq 19 | -----END CERTIFICATE REQUEST----- 20 | -------------------------------------------------------------------------------- /test/echo.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const net = require('net') 3 | const tls = require('tls') 4 | const { join } = require('path') 5 | const startTls = require('./starttls') 6 | const { PORT_NET, PORT_STARTTLS, PORT_TLS } = require('./constants') 7 | 8 | module.exports = function createServers () { 9 | const key = fs.readFileSync(join(__dirname, '..', 'crt', 'server.key'), 'utf8') 10 | const cert = fs.readFileSync(join(__dirname, '..', 'crt', 'server.crt'), 'utf8') 11 | const s1 = net.createServer(socket => { socket.pipe(socket) }) 12 | const s2 = tls.createServer({ key, cert }, socket => { socket.pipe(socket) }) 13 | const s3 = startTls.createServer(socket => { 14 | socket.upgrade({ key, cert, requestCert: false, rejectUnauthorized: false }, () => { 15 | socket.pipe(socket) 16 | }) 17 | }) 18 | 19 | const servers = [[s1, PORT_NET], [s2, PORT_TLS], [s3, PORT_STARTTLS]] 20 | const startServers = () => Promise.all(servers.map(([server, port]) => new Promise((resolve, reject) => { server.listen(port, resolve) }))) 21 | const stopServers = () => Promise.all(servers.map(([s, _]) => new Promise((resolve, reject) => { s.close(resolve) }))) 22 | return { startServers, stopServers } 23 | } 24 | -------------------------------------------------------------------------------- /src/timeout.js: -------------------------------------------------------------------------------- 1 | import { setTimeout } from 'core-js/library/web/timers' 2 | 3 | // setZeroTimeout slightly adapted from 4 | // https://github.com/shahyar/setZeroTimeout-js (CC BY 3.0). 5 | // Provides a function similar to setImmediate() on Chrome. 6 | const timeouts = [] 7 | const msgName = 'hackyVersionOfSetImmediate' 8 | 9 | function postTimeout (fn) { 10 | timeouts.push(fn) 11 | postMessage(msgName, '*') 12 | } 13 | function handleMessage (event) { 14 | if (event.source === window && event.data === msgName) { 15 | if (event.stopPropagation) { 16 | event.stopPropagation() 17 | } 18 | if (timeouts.length) { 19 | try { 20 | timeouts.shift()() 21 | } catch (e) { 22 | // Throw in an asynchronous closure to prevent setZeroTimeout from hanging due to error 23 | setTimeout((function (e) { 24 | return function () { 25 | throw e.stack || e 26 | } 27 | }(e)), 0) 28 | } 29 | } 30 | if (timeouts.length) { // more left? 31 | postMessage(msgName, '*') 32 | } 33 | } 34 | } 35 | 36 | let fn 37 | if (typeof setImmediate !== 'undefined') { 38 | fn = setImmediate 39 | } else if (typeof window !== 'undefined') { 40 | window.addEventListener('message', handleMessage, true) 41 | fn = postTimeout 42 | } else { 43 | fn = f => setTimeout(f, 0) 44 | } 45 | 46 | export default fn 47 | -------------------------------------------------------------------------------- /src/node-socket-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import TCPSocket from './node-socket' 4 | 5 | describe('TCPSocket Node.js socket unit tests', function () { 6 | var socket, nodeSocketStub 7 | 8 | beforeEach(function () { 9 | socket = TCPSocket.open('127.0.0.1', 9000, { 10 | useSecureTransport: false 11 | }) 12 | expect(socket).to.exist 13 | expect(socket._socket).to.exist 14 | 15 | var Socket = function () { } 16 | Socket.prototype.on = function () { } 17 | Socket.prototype.write = function () { } 18 | Socket.prototype.end = function () { } 19 | 20 | socket._socket = nodeSocketStub = sinon.createStubInstance(Socket) 21 | }) 22 | 23 | describe('open', function () { 24 | it('should not explode', function () { 25 | socket = TCPSocket.open('127.0.0.1', 9000, { 26 | useSecureTransport: false 27 | }) 28 | expect(socket).to.exist 29 | }) 30 | }) 31 | 32 | describe('close', function () { 33 | it('should not explode', function () { 34 | nodeSocketStub.end.returns() 35 | 36 | socket.close() 37 | expect(socket.readyState).to.equal('closing') 38 | }) 39 | }) 40 | 41 | describe('send', function () { 42 | it('should not explode', function (done) { 43 | nodeSocketStub.write.yields() 44 | 45 | socket.ondrain = function () { 46 | done() 47 | } 48 | 49 | socket.send(new ArrayBuffer()) 50 | }) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /crt/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID8DCCAtigAwIBAgIJALnqbvYxoZYrMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEyMTIxMTE1 5 | MDJaFw0xOTA0MjYxMTE1MDJaMIHCMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3 6 | IFlvcmsxEjAQBgNVBAcMCVJvY2hlc3RlcjESMBAGA1UECgwJRW5kIFBvaW50MRcw 7 | FQYDVQQLDA5UZXN0aW5nIERvbWFpbjFLMEkGCSqGSIb3DQEJARY8eW91ci1hZG1p 8 | bmlzdHJhdGl2ZS1hZGRyZXNzQHlvdXItYXdlc29tZS1leGlzdGluZy1kb21haW4u 9 | Y29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 10 | ggEKAoIBAQDoKjPR9RpvrI2F4xcrpGyvCOQOJuyV3WyXU1BpeoqszESVWqlckyE6 11 | 6Xr5igA5pk8brMGkXS6v/utdgyGc81cbdoPUP99GK+/d6igwJg08po8JuAS7+0Wd 12 | Lepp3TLa13e2In7mVQ1lBQ5+lsLn/N505lsaJG0ADvExM4qJvOHq+2w3BS4Ko32M 13 | KyjF2zyaeVOSNudVJsA2ooYecQ2Sj2TZjoXd1YPDyS0JWV1VOSvLa2KTbUCjy8PB 14 | zIax2YgeilIz/Bu2QAC1Z3Cm0ZzBA+7IP626rv1FfRlY5WvBmuikySFrZt8iQkRN 15 | /hWDPR425SX+qTjs3nBTqp9sBPhyqLl9AgMBAAGjUTBPMB8GA1UdIwQYMBaAFPap 16 | 6Ia1Joc2U+KZ1vCYfZ0jeibaMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1Ud 17 | EQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEABX00ZO3SouwkDoxQ 18 | Ox/vUTqNcbLD7qNvt8vXUXTp6pviV/ZSHrFLEBEwAdlYw02uANorXb86bHE31VJ3 19 | ORZl6aoSm00OatuF7xDi0fD4x0PCYCgExlQF54ttJi+dqYRP/QyShZrDUJ2l5CbS 20 | 5DdK9DCrpTrXNGmSc5pWIo/bosDaDiB/sgTRu8/WzyNzsIPkwAEVWy05Wk6rcdwV 21 | uQGuMGuYPG+3oZyVHYKKHMPF42PGw/Vs6O4h8I1Q2QsfNmm2GzqQVwW26LNsKsti 22 | BdEBYoOldyx+Ul+607hCnDD4qVjuJcbRc5r9Q2w25SNDTXpPtAERkq1Q3M2GT/Of 23 | ERiojg== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /wdio.conf.js: -------------------------------------------------------------------------------- 1 | const attachProxy = require('emailjs-tcp-proxy').default 2 | const express = require('express') 3 | const { Server } = require('http') 4 | const path = require('path') 5 | const echo = require('./test/echo') 6 | 7 | const { startServers, stopServers } = echo() 8 | const app = express() 9 | const server = Server(app) 10 | app.use('/', express.static(path.join(__dirname, 'test', 'ws'))) 11 | attachProxy(server) 12 | 13 | exports.config = { 14 | specs: [ 15 | './test/ws/*-integration.js' 16 | ], 17 | maxInstances: 1, 18 | capabilities: [{ 19 | maxInstances: 1, 20 | browserName: 'chrome' 21 | }], 22 | sync: true, 23 | logLevel: 'error', 24 | coloredLogs: true, 25 | deprecationWarnings: true, 26 | bail: 0, 27 | screenshotPath: './test/ws/error-shots/', 28 | baseUrl: 'http://localhost:12345/', 29 | waitforTimeout: 100000, 30 | connectionRetryTimeout: 90000, 31 | connectionRetryCount: 3, 32 | services: ['chromedriver'], 33 | framework: 'mocha', 34 | port: '9515', 35 | path: '/', 36 | mochaOpts: { 37 | ui: 'bdd' 38 | }, 39 | beforeSession: function (config, capabilities, specs) { 40 | startServers() 41 | server.listen(12345) 42 | }, 43 | before: function (capabilities, specs) { 44 | var chai = require('chai') 45 | global.expect = chai.expect 46 | }, 47 | after: function (result, capabilities, specs) { 48 | }, 49 | afterSession: function (config, capabilities, specs) { 50 | server.close() 51 | stopServers() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crt/rootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID9DCCAtygAwIBAgIJAMolSx3RAHk8MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEyMTIxMTE0 5 | NTdaFw0yMDEwMDExMTE0NTdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l 6 | LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL8D 8 | k/ldkChPVtLdXj5BD2iz36aS8pxwbAwPv2gm/Ba93ztLSZSSebxd7rQfoWmCDEGj 9 | ZclUBG0zjmc/oJMHjmxEHjM6hUM2ac6wYXR0Pb7A/0/lIiRq10YBa60kb5b8YYWx 10 | G6sW9E+2yMnLRZUieodQYLBYq/y4UZGCO4SiOkYEnhcRtdRnUKOVSbCxPQrIngpv 11 | EnmIFDago8SDwjYIS1X0pWN4KIwPEVje97BzKARC+d47yni1iKxZyE+0WdU6VyGh 12 | irxPiRvcMjK3Iz4kHbRVij0dBtyoaqxyGN9HJ1c3Ga5bz6G12koab4ELU7/95fsi 13 | tmScAhQNHS3YzKNLx58CAwEAAaOBvjCBuzAdBgNVHQ4EFgQU9qnohrUmhzZT4pnW 14 | 8Jh9nSN6JtowgYsGA1UdIwSBgzCBgIAU9qnohrUmhzZT4pnW8Jh9nSN6JtqhXaRb 15 | MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ 16 | bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAMol 17 | Sx3RAHk8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH4rEup8noqn 18 | 9eRecfxvvrw69O2ztqb4SQG8MPy5fw1Pghe+bl/JHj6qL/hgTMLo0+kA5/6VfWwz 19 | QDs1ocwjQn2BQOgyaq7bqRMGKSyaTzcXHu4LkSikbU9LCXrLp/wz7J3UCaYezOXc 20 | 2I0z0k+W4uu1GoxZD8Bmb5LOBtK4CFodYGThW7mTSLCM7a34dwCUQ2UAIKHu6T1d 21 | MdFX5vXfLhzZ+JaCVZLwmIc49vH1tp+Pj6F0gyW798VW4pwGgBciaatBHRUpVprr 22 | dwM92OxmRDZ8bEVdKquSnaeckwmGHmS+hjnqoUBn66VzhHDXpEDzwM5fcmq6HmGN 23 | rjkU+KD74qs= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /crt/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA6Coz0fUab6yNheMXK6RsrwjkDibsld1sl1NQaXqKrMxElVqp 3 | XJMhOul6+YoAOaZPG6zBpF0ur/7rXYMhnPNXG3aD1D/fRivv3eooMCYNPKaPCbgE 4 | u/tFnS3qad0y2td3tiJ+5lUNZQUOfpbC5/zedOZbGiRtAA7xMTOKibzh6vtsNwUu 5 | CqN9jCsoxds8mnlTkjbnVSbANqKGHnENko9k2Y6F3dWDw8ktCVldVTkry2tik21A 6 | o8vDwcyGsdmIHopSM/wbtkAAtWdwptGcwQPuyD+tuq79RX0ZWOVrwZropMkha2bf 7 | IkJETf4Vgz0eNuUl/qk47N5wU6qfbAT4cqi5fQIDAQABAoIBADcJSh+LqyiuDx6e 8 | JvABmoIj6WwxbgRY6VU2OunGUvmDsv5075Eyj74Xez+Mp2EDO92jpoQAUwEFvWX9 9 | oApE5XFtNvMp8RQBdmt1BvHjE2A9W8tCBF1Lw8TvDZLrDRMz4P0cUFjp8LLx7+2y 10 | b6HvIA02ToJ0ACLzZ9nyDfV5AVVisTQwJwzTiB980lllSYwrJM0VnJcV6yEN5rO+ 11 | zhJxr8P1KvgCP5fmI2MjBkFxoYmGThFmO+y6NmVhx0ly2Ee+z4F8Zsk+wQ/Ew1lx 12 | /4ykAwOt/JRCsqG6PIZO05D46KSO4+ztzWlP54FtD9OfpSnEdEForq0IV5xWHT8s 13 | 337R20ECgYEA+kpyIQLMHIxXP4CZgCe3kKSXJ48W5MQkwis6tP5D3qY+L7WqlnX7 14 | eS8lVwrEs5eAK9gfhD639CFD34qYdYFDTQ9g514ShSgB472ftaHc4UxFA8Fi9smB 15 | zMmv623TAoYcuXiySxshQ4cFupNxCQi+omegDYf8W6zc4Sa02PH03+cCgYEA7XXp 16 | V22ypo1S9Uu42wHW1k1rY0nX7YYdq6rh8w4acx/8g9BrZRhz4ITwo+CaYqnnIk3R 17 | 3dQ8GO1iNy67o7VqfibIakx8aBEfogg3jggoHOmjqBddT/fyXSj9tZZHFNOsh6eJ 18 | sn5XTPLseHQn80dNTXItBiA4N2AvIl50yq4G/vsCgYEA9VdeMk+cpmtG5t93GDFW 19 | dblw1X0kktBVu+NGI76N5sUMdhXchqpV+78kGgNbNzPNlqy+kBIunWayMoCX267+ 20 | up3VflYvT0kdORFaaV+ltU7e48R/7qstygD1qZA44+N0arYOByMr4xaKng4DIjjp 21 | LSbos/rVe0OiLC2V3oamY4cCgYEA0DAafk5s3Nz2qJzU2x5Hrud9iTVKnLKC/Oj6 22 | 5E+vHNQkXaSzI5VGssea+vKGKI5xWIAFyYizj98/xsWwOR1q2mNCPwAD6nFo3HR7 23 | 0IiSMpccptilNOFbmhXAKh4w/699igl3Dgj6nTo13H1qMT6IJag+lSpWXyZy7J0H 24 | pFLjS1UCgYBRyT6iMs2NfqvE1uwrw0sHpaKTE90L+TEfPXxdhjNWmirKKB1Wpllp 25 | irChddOXa9E3mCHnpdZq1JMP833iRjuc89KhBYl/iVbeVLHBBk3GmFyaMBjIQFhL 26 | T3AR3Q7Z9jGM4TGZc3G23FpmGLgOnnSAykm6EziCgxIm6fPyAZTtdQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /crt/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,9AD937C7F68374EE 4 | 5 | uwzN0kioEHHROiQ1aTm6v2yYo0sXQ6V7g95U9v9fSUqCrSlxSpmQ00FaEM1+RuUb 6 | MptbmoQ8QdEuaGhBbuFnS2tsWVQune0vAKRYMbGhGvK34BajN59jfJn7gJu2eB3j 7 | +AWnaoXyxQ2pkEcQuiLeShkvylwLzn3vCCvGm9Mov0NmkUbRMK8wuPvUlbvc/QhF 8 | 7xJhR5wM6HDRBl+nnMlqp/gxw9rlP9rFPqSn7RU2jDXkdd+nTxb0u4+tyhIc5WEK 9 | sFaCpXR76TXe1mYvTKS3C68c7Gq+rLxgeQNsMdVnQpAvyIwLCNh5+bk0Uop2SK33 10 | Sm3q3FtRDW9OvftU41hGgquT5pw5RVaYAofcdyj5Bt/myuQD3C6X30jFlY0G/0Ne 11 | dMHSCLE2mwu9XCuVXoI4U0KhfmCvK+72qLTuBiYWQZHGCBUibxAHnnViEf24q2kS 12 | KccvYn9Tg9O8ff/3KB45pOTK1V4EY1dhqop1jOkm+exc7IZBihTwOnotNrYmR1I1 13 | FifUXaabVq+5Y9QIGIfkAtug64HY509IkolY5C3MnSGvUX6eY4RFgK+Q9KGIR9Tk 14 | dKCTTCSucSDTT0UcXys2zc9jbZ+Vog5qglCZa6G0exc4og1dxomaM05I7+qIBs3D 15 | szeSE2LPATrzplH5m3zKXWkpgZYpiRtmJR+lFvPytNosgcm41cNkBWX2p6s/LfMz 16 | aKMoDrPUhsB5ITelVSOWAX3aaYBShYIJ1SEk0RGyYsI+BiYbO6coZxLEpxInsvZB 17 | bpjIBAlJk2v5iSzDWeuod9fkE3Tp8HZEKj5yAH3jhmof5rgzaB6asxZqqCbRjwHH 18 | ZUyySsHsCy6ngu/vTD7OTe511FTn+f41/xybf92ve9BUgJC55s+9Z4z49n6QV11x 19 | YO6nLEcq7JF/jMwZ8l8Sj5/ZlnWR6LCK2VF7DV/6OunugrkcqEo+1YnXq8C37qvT 20 | eQd3QFOE0jEJ5bodTh+xn5UCPjIC/RGC/FoObfBRgK/DgmVXmkBSFzwFzyxq4s2Y 21 | s6r22XjArMqRmtDu3q0wuPhUQeAHsA/WvJ/CnzftpCWHQXRHIAdACmytPo4YTAkA 22 | r7ptsbdK/+nd6iKa7UY3RehBTorgQf7sgIEX59F7G7ywpeMRyAUWNe5JE6ttVHLa 23 | dD7KFXMWzMXeeYhEZ9vP6ede8822XSgAO9c6PPqG4LdyNj0DS2f0rra0ZBGxPwsF 24 | DTnAP/JSr2GtvZ0ZTyr9qP0/UNI+Llvk2MCewZtS8qBlhVCks2son12z9WjIlzzt 25 | /e6bRoESnPewx6rPOYpVx+IYA3QNzzHfyL9ZJE4ULX1iiQCASH2giVBn7npvbNYb 26 | hVPZjair2Sk0iimDx26vGUlnHYVuAqEy/pkQ4eXDMeu3dGTf+ETDXfC3B7A/E7RW 27 | Ti/qDcZX+u5tNAe4zi5SDZ6Xb2892zXSWtMN3v0r4IEPf1lF9qUT5janEJi7ID+P 28 | Z1Vc64+iSnazRv693EMK0ZXEFnQ5NGIsxkLPD1BT/RvQDuC8AfTAGqW31mi8R2ed 29 | s1UeVWdli1xoGzs9T1tWyoOvLzl2AXBkGszV4VdB71R+kLKQxEzEAA== 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /dist/worker-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var EVENT_INBOUND = exports.EVENT_INBOUND = 'inbound'; 7 | var EVENT_OUTBOUND = exports.EVENT_OUTBOUND = 'outbound'; 8 | var EVENT_OPEN = exports.EVENT_OPEN = 'open'; 9 | var EVENT_CLOSE = exports.EVENT_CLOSE = 'close'; 10 | var EVENT_ERROR = exports.EVENT_ERROR = 'error'; 11 | var EVENT_CONFIG = exports.EVENT_CONFIG = 'configure'; 12 | var EVENT_CERT = exports.EVENT_CERT = 'cert'; 13 | var EVENT_HANDSHAKE = exports.EVENT_HANDSHAKE = 'handshake'; 14 | 15 | var createMessage = exports.createMessage = function createMessage(event, message) { 16 | return { event: event, message: message }; 17 | }; 18 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy93b3JrZXItdXRpbHMuanMiXSwibmFtZXMiOlsiRVZFTlRfSU5CT1VORCIsIkVWRU5UX09VVEJPVU5EIiwiRVZFTlRfT1BFTiIsIkVWRU5UX0NMT1NFIiwiRVZFTlRfRVJST1IiLCJFVkVOVF9DT05GSUciLCJFVkVOVF9DRVJUIiwiRVZFTlRfSEFORFNIQUtFIiwiY3JlYXRlTWVzc2FnZSIsImV2ZW50IiwibWVzc2FnZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBTyxJQUFNQSx3Q0FBZ0IsU0FBdEI7QUFDQSxJQUFNQywwQ0FBaUIsVUFBdkI7QUFDQSxJQUFNQyxrQ0FBYSxNQUFuQjtBQUNBLElBQU1DLG9DQUFjLE9BQXBCO0FBQ0EsSUFBTUMsb0NBQWMsT0FBcEI7QUFDQSxJQUFNQyxzQ0FBZSxXQUFyQjtBQUNBLElBQU1DLGtDQUFhLE1BQW5CO0FBQ0EsSUFBTUMsNENBQWtCLFdBQXhCOztBQUVBLElBQU1DLHdDQUFnQixTQUFoQkEsYUFBZ0IsQ0FBQ0MsS0FBRCxFQUFRQyxPQUFSO0FBQUEsU0FBcUIsRUFBRUQsWUFBRixFQUFTQyxnQkFBVCxFQUFyQjtBQUFBLENBQXRCIiwiZmlsZSI6Indvcmtlci11dGlscy5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBjb25zdCBFVkVOVF9JTkJPVU5EID0gJ2luYm91bmQnXG5leHBvcnQgY29uc3QgRVZFTlRfT1VUQk9VTkQgPSAnb3V0Ym91bmQnXG5leHBvcnQgY29uc3QgRVZFTlRfT1BFTiA9ICdvcGVuJ1xuZXhwb3J0IGNvbnN0IEVWRU5UX0NMT1NFID0gJ2Nsb3NlJ1xuZXhwb3J0IGNvbnN0IEVWRU5UX0VSUk9SID0gJ2Vycm9yJ1xuZXhwb3J0IGNvbnN0IEVWRU5UX0NPTkZJRyA9ICdjb25maWd1cmUnXG5leHBvcnQgY29uc3QgRVZFTlRfQ0VSVCA9ICdjZXJ0J1xuZXhwb3J0IGNvbnN0IEVWRU5UX0hBTkRTSEFLRSA9ICdoYW5kc2hha2UnXG5cbmV4cG9ydCBjb25zdCBjcmVhdGVNZXNzYWdlID0gKGV2ZW50LCBtZXNzYWdlKSA9PiAoeyBldmVudCwgbWVzc2FnZSB9KVxuIl19 -------------------------------------------------------------------------------- /test/chrome/remote-debugger.js: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/tejohnso/chrome-app-test-runner 2 | 3 | import WebSocket from 'ws' 4 | import { get } from 'http' 5 | 6 | let ws 7 | let messageHandler 8 | let intervalHandler 9 | let evalPromiseResolverObject = { resolve: 0 } 10 | 11 | function createConnectionResponse (resolver, intervalHandler) { 12 | return function (resp) { 13 | var chunks = '' 14 | 15 | resp.on('data', function (data) { 16 | chunks += data 17 | }) 18 | resp.on('end', function () { 19 | var inspectables 20 | 21 | inspectables = JSON.parse(chunks).filter(function (tabData) { 22 | return tabData.type === 'app' 23 | })[0] 24 | 25 | if (inspectables && inspectables.webSocketDebuggerUrl) { 26 | clearInterval(intervalHandler.handle) 27 | ws = new WebSocket(inspectables.webSocketDebuggerUrl) 28 | ws.onopen = function () { 29 | ws.send(JSON.stringify({ 'id': 1, 'method': 'Console.enable' })) 30 | } 31 | ws.onmessage = function (event) { 32 | var data = JSON.parse(event.data) 33 | 34 | if (data.id === 9) { 35 | return evalPromiseResolverObject.resolver(data.result.result.value) 36 | } 37 | messageHandler(data) 38 | } 39 | resolver() 40 | } 41 | }) 42 | } 43 | } 44 | 45 | function createErrorResponse (rejecter) { 46 | return function (resp) { 47 | console.log(resp) 48 | clearInterval(intervalHandler.handle) 49 | rejecter() 50 | } 51 | } 52 | 53 | export function attachDebugger () { 54 | return new Promise(function (resolve, reject) { 55 | intervalHandler = { handle: 0 } 56 | let connectionResponse = createConnectionResponse(resolve, intervalHandler) 57 | let errorResponse = createErrorResponse(reject) 58 | 59 | intervalHandler.handle = setInterval(function () { 60 | get('http://localhost:9222/json/list', connectionResponse) 61 | .on('error', errorResponse) 62 | }, 500) 63 | }) 64 | } 65 | 66 | export function setDebugHandler (handler) { 67 | messageHandler = handler 68 | } 69 | -------------------------------------------------------------------------------- /test/chrome/chrome-integration.js: -------------------------------------------------------------------------------- 1 | import TCPSocket from '../../' 2 | import { PORT_NET, PORT_STARTTLS, PORT_TLS } from '../constants' 3 | const { expect } = window.chai 4 | 5 | const a2s = arr => String.fromCharCode.apply(null, new Uint8Array(arr)) 6 | const s2a = str => new Uint8Array(str.split('').map(char => char.charCodeAt(0))).buffer 7 | 8 | describe('TCP chrome shim integration tests', function () { 9 | const payload = 'the.payload.woopwoop!' 10 | let received 11 | 12 | beforeEach(done => { 13 | received = '' 14 | setTimeout(done, 500) 15 | }) 16 | 17 | describe('tcp', function () { 18 | it('should open, read, write, and close', function (done) { 19 | const socket = TCPSocket.open('localhost', PORT_NET) 20 | socket.onopen = () => { socket.send(s2a(payload)) } 21 | socket.onclose = () => { 22 | expect(received).to.equal(payload) 23 | done() 24 | } 25 | socket.ondata = ({ data }) => { 26 | received += a2s(data) 27 | if (received === payload) { 28 | socket.close() 29 | } 30 | } 31 | }) 32 | }) 33 | 34 | describe('tls', function () { 35 | it('should open, read, write, and close', function (done) { 36 | const useSecureTransport = true 37 | var socket = TCPSocket.open('localhost', PORT_TLS, { useSecureTransport }) 38 | socket.onopen = () => { socket.send(s2a(payload)) } 39 | socket.onclose = () => { 40 | expect(received).to.equal(payload) 41 | done() 42 | } 43 | socket.ondata = ({ data }) => { 44 | received += a2s(data) 45 | if (received === payload) { 46 | socket.close() 47 | } 48 | } 49 | }) 50 | }) 51 | 52 | describe.skip('starttls', function () { 53 | it('should open, read, write, and close', function (done) { 54 | var socket = TCPSocket.open('localhost', PORT_STARTTLS) 55 | socket.onopen = () => { 56 | socket.upgradeToSecure() 57 | socket.send(s2a(payload)) 58 | } 59 | socket.onclose = () => { 60 | expect(received).to.equal(payload) 61 | done() 62 | } 63 | socket.ondata = ({ data }) => { 64 | received += a2s(data) 65 | if (received === payload) { 66 | socket.close() 67 | } 68 | } 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/node/node-integration.js: -------------------------------------------------------------------------------- 1 | const echo = require('../echo') 2 | const TCPSocket = require('../../').default 3 | const { PORT_NET, PORT_STARTTLS, PORT_TLS } = require('../constants') 4 | 5 | const { startServers, stopServers } = echo() 6 | 7 | const a2s = arr => String.fromCharCode.apply(null, new Uint8Array(arr)) 8 | const s2a = str => new Uint8Array(str.split('').map(char => char.charCodeAt(0))).buffer 9 | 10 | describe('TCP node shim integration tests', function () { 11 | const payload = 'the.payload.woopwoop!' 12 | let received 13 | 14 | before(() => startServers()) 15 | 16 | beforeEach(() => { 17 | received = '' 18 | }) 19 | 20 | after(() => stopServers()) 21 | 22 | describe('tcp', function () { 23 | it('should open, read, write, and close', function (done) { 24 | const socket = TCPSocket.open('localhost', PORT_NET) 25 | socket.onopen = () => { socket.send(s2a(payload)) } 26 | socket.onclose = () => { 27 | expect(received).to.equal(payload) 28 | done() 29 | } 30 | socket.ondata = ({ data }) => { 31 | received += a2s(data) 32 | if (received === payload) { 33 | socket.close() 34 | } 35 | } 36 | }) 37 | }) 38 | 39 | describe('tls', function () { 40 | it('should open, read, write, and close', function (done) { 41 | const useSecureTransport = true 42 | var socket = TCPSocket.open('localhost', PORT_TLS, { useSecureTransport }) 43 | socket.onopen = () => { socket.send(s2a(payload)) } 44 | socket.onclose = () => { 45 | expect(received).to.equal(payload) 46 | done() 47 | } 48 | socket.ondata = ({ data }) => { 49 | received += a2s(data) 50 | if (received === payload) { 51 | socket.close() 52 | } 53 | } 54 | }) 55 | }) 56 | 57 | describe.skip('starttls', function () { 58 | it('should open, read, write, and close', function (done) { 59 | var socket = TCPSocket.open('localhost', PORT_STARTTLS) 60 | socket.onopen = () => { 61 | socket.upgradeToSecure() 62 | socket.send(s2a(payload)) 63 | } 64 | socket.onclose = () => { 65 | expect(received).to.equal(payload) 66 | done() 67 | } 68 | socket.ondata = ({ data }) => { 69 | received += a2s(data) 70 | if (received === payload) { 71 | socket.close() 72 | } 73 | } 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /src/tls-utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | EVENT_HANDSHAKE, 3 | EVENT_INBOUND, EVENT_OUTBOUND, 4 | EVENT_OPEN, EVENT_CLOSE, 5 | EVENT_CONFIG, EVENT_CERT, 6 | EVENT_ERROR, 7 | createMessage 8 | } from './worker-utils' 9 | import TLS from './tls' 10 | import TlsWorkerBlob from '../res/tls.worker.blob' 11 | 12 | export default function createTls (socket) { 13 | socket.tlscert = cert => { socket.oncert(cert) } 14 | socket.tlsclose = () => { socket.close() } 15 | socket.tlsoutbound = buffer => { socket._send(buffer) } 16 | socket.tlsinbound = buffer => { socket._emit('data', buffer) } 17 | socket.tlserror = function (message) { 18 | socket._emit('error', new Error(message)) 19 | socket.close() 20 | } 21 | socket.tlsopen = function () { 22 | socket.ssl = true 23 | if (socket._useTLS) { 24 | if (socket._proxyHostname) { 25 | socket._emit('open', { 26 | proxyHostname: socket._proxyHostname 27 | }) 28 | } else { 29 | socket._emit('open') 30 | } 31 | } 32 | } 33 | 34 | if (window.Worker) { 35 | createTlsWithWorker(socket) 36 | } else { 37 | createTlsNoWorker(socket) 38 | } 39 | } 40 | 41 | var createTlsNoWorker = function (socket) { 42 | socket._tls = new TLS() 43 | socket._tls.tlserror = socket.tlserror 44 | socket._tls.tlscert = socket.tlscert 45 | socket._tls.tlsclose = socket.tlsclose 46 | socket._tls.tlsopen = socket.tlsopen 47 | socket._tls.tlsoutbound = socket.tlsoutbound 48 | socket._tls.tlsinbound = socket.tlsinbound 49 | 50 | // configure the tls client 51 | socket._tls.configure({ 52 | host: socket.host, 53 | ca: socket._ca 54 | }) 55 | 56 | // start the handshake 57 | socket._tls.handshake() 58 | } 59 | 60 | var createTlsWithWorker = function (socket) { 61 | socket._tlsWorker = new Worker(URL.createObjectURL(new Blob([TlsWorkerBlob]))) 62 | socket._tlsWorker.onerror = ({ message }) => socket.tlserror(message) 63 | socket._tlsWorker.onmessage = function ({ data: { event, message } }) { 64 | switch (event) { 65 | case EVENT_CERT: 66 | socket.tlscert(message) 67 | break 68 | case EVENT_ERROR: 69 | socket.tlserror(message) 70 | break 71 | case EVENT_CLOSE: 72 | socket.tlsclose(message) 73 | break 74 | case EVENT_OPEN: 75 | socket.tlsopen(message) 76 | break 77 | case EVENT_OUTBOUND: 78 | socket.tlsoutbound(message) 79 | break 80 | case EVENT_INBOUND: 81 | socket.tlsinbound(message) 82 | break 83 | } 84 | } 85 | 86 | socket._tlsWorker.postMessage(createMessage(EVENT_CONFIG, { host: socket.host, ca: socket._ca })) 87 | socket._tlsWorker.postMessage(createMessage(EVENT_HANDSHAKE)) 88 | } 89 | -------------------------------------------------------------------------------- /test/ws/index.js: -------------------------------------------------------------------------------- 1 | import TCPSocket from '../../' 2 | import { PORT_NET, PORT_TLS } from '../constants' 3 | 4 | const a2s = arr => String.fromCharCode.apply(null, new Uint8Array(arr)) 5 | const s2a = str => new Uint8Array(str.split('').map(char => char.charCodeAt(0))).buffer 6 | const ca = '-----BEGIN CERTIFICATE-----\r\n' + 7 | 'MIID8DCCAtigAwIBAgIJALnqbvYxoZYrMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV\r\n' + 8 | 'BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX\r\n' + 9 | 'aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEyMTIxMTE1\r\n' + 10 | 'MDJaFw0xOTA0MjYxMTE1MDJaMIHCMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3\r\n' + 11 | 'IFlvcmsxEjAQBgNVBAcMCVJvY2hlc3RlcjESMBAGA1UECgwJRW5kIFBvaW50MRcw\r\n' + 12 | 'FQYDVQQLDA5UZXN0aW5nIERvbWFpbjFLMEkGCSqGSIb3DQEJARY8eW91ci1hZG1p\r\n' + 13 | 'bmlzdHJhdGl2ZS1hZGRyZXNzQHlvdXItYXdlc29tZS1leGlzdGluZy1kb21haW4u\r\n' + 14 | 'Y29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\r\n' + 15 | 'ggEKAoIBAQDoKjPR9RpvrI2F4xcrpGyvCOQOJuyV3WyXU1BpeoqszESVWqlckyE6\r\n' + 16 | '6Xr5igA5pk8brMGkXS6v/utdgyGc81cbdoPUP99GK+/d6igwJg08po8JuAS7+0Wd\r\n' + 17 | 'Lepp3TLa13e2In7mVQ1lBQ5+lsLn/N505lsaJG0ADvExM4qJvOHq+2w3BS4Ko32M\r\n' + 18 | 'KyjF2zyaeVOSNudVJsA2ooYecQ2Sj2TZjoXd1YPDyS0JWV1VOSvLa2KTbUCjy8PB\r\n' + 19 | 'zIax2YgeilIz/Bu2QAC1Z3Cm0ZzBA+7IP626rv1FfRlY5WvBmuikySFrZt8iQkRN\r\n' + 20 | '/hWDPR425SX+qTjs3nBTqp9sBPhyqLl9AgMBAAGjUTBPMB8GA1UdIwQYMBaAFPap\r\n' + 21 | '6Ia1Joc2U+KZ1vCYfZ0jeibaMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1Ud\r\n' + 22 | 'EQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEABX00ZO3SouwkDoxQ\r\n' + 23 | 'Ox/vUTqNcbLD7qNvt8vXUXTp6pviV/ZSHrFLEBEwAdlYw02uANorXb86bHE31VJ3\r\n' + 24 | 'ORZl6aoSm00OatuF7xDi0fD4x0PCYCgExlQF54ttJi+dqYRP/QyShZrDUJ2l5CbS\r\n' + 25 | '5DdK9DCrpTrXNGmSc5pWIo/bosDaDiB/sgTRu8/WzyNzsIPkwAEVWy05Wk6rcdwV\r\n' + 26 | 'uQGuMGuYPG+3oZyVHYKKHMPF42PGw/Vs6O4h8I1Q2QsfNmm2GzqQVwW26LNsKsti\r\n' + 27 | 'BdEBYoOldyx+Ul+607hCnDD4qVjuJcbRc5r9Q2w25SNDTXpPtAERkq1Q3M2GT/Of\r\n' + 28 | 'ERiojg==\r\n' + 29 | '-----END CERTIFICATE-----' 30 | 31 | window.onload = () => { 32 | const net = TCPSocket.open('localhost', PORT_NET) 33 | net.onopen = () => { 34 | net.send(s2a('payload')) 35 | } 36 | net.ondata = ({ data }) => { 37 | const incomingData = a2s(data) 38 | const elem = document.createElement('textarea') 39 | elem.innerText = incomingData 40 | elem.id = 'plaintext' 41 | document.body.appendChild(elem) 42 | } 43 | 44 | const useSecureTransport = true 45 | const tls = TCPSocket.open('localhost', PORT_TLS, { useSecureTransport, ca }) 46 | tls.onopen = () => { 47 | tls.send(s2a('payload')) 48 | } 49 | tls.ondata = ({ data }) => { 50 | const incomingData = a2s(data) 51 | const elem = document.createElement('textarea') 52 | elem.innerText = incomingData 53 | elem.id = 'tls' 54 | document.body.appendChild(elem) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/node-socket.js: -------------------------------------------------------------------------------- 1 | import { propOr } from 'ramda' 2 | import net from 'net' 3 | import tls from 'tls' 4 | 5 | export default class TCPSocket { 6 | static open (host, port, options = {}) { 7 | return new TCPSocket({ host, port, options }) 8 | } 9 | 10 | constructor ({ host, port, options }) { 11 | this.host = host 12 | this.port = port 13 | this.ssl = propOr(false, 'useSecureTransport')(options) 14 | this.bufferedAmount = 0 15 | this.readyState = 'connecting' 16 | this.binaryType = propOr('arraybuffer', 'binaryType')(options) 17 | 18 | if (this.binaryType !== 'arraybuffer') { 19 | throw new Error('Only arraybuffers are supported!') 20 | } 21 | 22 | this._socket = this.ssl 23 | ? tls.connect({ 24 | port: this.port, 25 | host: this.host, 26 | servername: this.host // SNI 27 | }, () => this._emit('open')) 28 | : net.connect(this.port, this.host, () => this._emit('open')) 29 | 30 | // add all event listeners to the new socket 31 | this._attachListeners() 32 | } 33 | 34 | _attachListeners () { 35 | this._socket.on('data', nodeBuf => this._emit('data', nodeBuffertoArrayBuffer(nodeBuf))) 36 | this._socket.on('error', error => { 37 | // Ignore ECONNRESET errors. For the app this is the same as normal close 38 | if (error.code !== 'ECONNRESET') { 39 | this._emit('error', error) 40 | } 41 | this.close() 42 | }) 43 | 44 | this._socket.on('end', () => this._emit('close')) 45 | } 46 | 47 | _removeListeners () { 48 | this._socket.removeAllListeners('data') 49 | this._socket.removeAllListeners('end') 50 | this._socket.removeAllListeners('error') 51 | } 52 | 53 | _emit (type, data) { 54 | const target = this 55 | switch (type) { 56 | case 'open': 57 | this.readyState = 'open' 58 | this.onopen && this.onopen({ target, type, data }) 59 | break 60 | case 'error': 61 | this.onerror && this.onerror({ target, type, data }) 62 | break 63 | case 'data': 64 | this.ondata && this.ondata({ target, type, data }) 65 | break 66 | case 'drain': 67 | this.ondrain && this.ondrain({ target, type, data }) 68 | break 69 | case 'close': 70 | this.readyState = 'closed' 71 | this.onclose && this.onclose({ target, type, data }) 72 | break 73 | } 74 | } 75 | 76 | // 77 | // API 78 | // 79 | 80 | close () { 81 | this.readyState = 'closing' 82 | this._socket.end() 83 | } 84 | 85 | send (data) { 86 | // convert data to string or node buffer 87 | this._socket.write(arrayBufferToNodeBuffer(data), this._emit.bind(this, 'drain')) 88 | } 89 | 90 | upgradeToSecure () { 91 | if (this.ssl) return 92 | 93 | this._removeListeners() 94 | this._socket = tls.connect({ socket: this._socket }, () => { this.ssl = true }) 95 | this._attachListeners() 96 | } 97 | } 98 | 99 | const nodeBuffertoArrayBuffer = buf => Uint8Array.from(buf).buffer 100 | const arrayBufferToNodeBuffer = (ab) => Buffer.from(new Uint8Array(ab)) 101 | -------------------------------------------------------------------------------- /src/tls-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import TLS from './tls' 4 | 5 | describe('TlsClient unit tests', function () { 6 | describe('#verifyCertificate', function () { 7 | // Forge mocks 8 | const certNoAltWildcard = { 9 | subject: { 10 | getField: () => ({ value: '*.wmail.io' }) 11 | }, 12 | getExtension: () => false 13 | } 14 | 15 | const certAlt = { 16 | subject: { 17 | getField: () => ({ value: '*.wmail.io' }) 18 | }, 19 | getExtension: () => ({ 20 | altNames: [{ 21 | value: '*.wmail.io' 22 | }, { 23 | value: 'wmail.io' 24 | }] 25 | }) 26 | } 27 | 28 | const certNoAltExact = { 29 | subject: { 30 | getField: () => ({ value: 'imap.wmail.io' }) 31 | }, 32 | getExtension: () => false 33 | } 34 | 35 | it('should validate certificate hostname from CN', function () { 36 | expect(TLS.prototype.verifyCertificate(certNoAltExact, 'imap.wmail.io')).to.be.true 37 | }) 38 | 39 | it('should validate certificate hostname from wildcard CN', function () { 40 | expect(TLS.prototype.verifyCertificate(certNoAltWildcard, 'wild.wmail.io')).to.be.true 41 | }) 42 | 43 | it('should validate certificate hostname from wildcard SAN', function () { 44 | expect(TLS.prototype.verifyCertificate(certAlt, 'wild.wmail.io')).to.be.true 45 | }) 46 | 47 | it('should validate certificate hostname from exact SAN', function () { 48 | expect(TLS.prototype.verifyCertificate(certAlt, 'wmail.io')).to.be.true 49 | }) 50 | 51 | it('should not validate certificate hostname from CN', function () { 52 | expect(TLS.prototype.verifyCertificate(certNoAltExact, 'wmail.com')).to.be.false 53 | expect(TLS.prototype.verifyCertificate(certNoAltExact, 'foo')).to.be.false 54 | }) 55 | 56 | it('should not validate certificate hostname from wildcard CN', function () { 57 | expect(TLS.prototype.verifyCertificate(certNoAltWildcard, 'wmail.com')).to.be.false 58 | expect(TLS.prototype.verifyCertificate(certNoAltWildcard, 'foo')).to.be.false 59 | }) 60 | 61 | it('should not validate certificate hostname from wildcard SAN', function () { 62 | expect(TLS.prototype.verifyCertificate(certAlt, 'wmail.com')).to.be.false 63 | expect(TLS.prototype.verifyCertificate(certAlt, 'foo')).to.be.false 64 | }) 65 | 66 | it('should not validate certificate hostname from exact SAN', function () { 67 | expect(TLS.prototype.verifyCertificate(certAlt, 'wmail.com')).to.be.false 68 | expect(TLS.prototype.verifyCertificate(certAlt, 'foo')).to.be.false 69 | }) 70 | }) 71 | 72 | describe('#compareServername', function () { 73 | it('should find exact match', function () { 74 | expect(TLS.prototype.compareServername('imap.wmail.io', 'imap.wmail.io')).to.be.true 75 | expect(TLS.prototype.compareServername('imap.wmail.io', 'no-imap.wmail.io')).to.be.false 76 | }) 77 | 78 | it('should find wildcard match', function () { 79 | expect(TLS.prototype.compareServername('imap.wmail.io', '*.wmail.io')).to.be.true 80 | expect(TLS.prototype.compareServername('imap.wmail.io', 'imap.*.io')).to.be.false 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emailjs-tcp-socket", 3 | "version": "2.0.2", 4 | "main": "dist/socket", 5 | "description": "This shim brings the W3C Raw Socket API to node.js and Chromium. Its purpose is to enable apps to use the same api in Firefox OS, Chrome OS, and on the server.", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/emailjs/emailjs-tcp-socket.git" 9 | }, 10 | "author": "Felix Hammerl", 11 | "maintainers": [ 12 | { 13 | "name": "Felix Hammerl", 14 | "email": "felix.hammerl@gmail.com" 15 | } 16 | ], 17 | "license": "MIT", 18 | "scripts": { 19 | "build": "npm run build-worker && rm -rf dist && babel src --out-dir dist --ignore '**/*-unit.js' --source-maps inline", 20 | "build-worker": "rm -f res/tls.worker.blob && npx webpack --config webpack.config.worker.js -p && mv res/tls.worker.js res/tls.worker.blob", 21 | "lint": "npx standard", 22 | "preversion": "npm run build", 23 | "test": "npm run lint && npm run unit && npm run integration", 24 | "unit": "npx mocha './src/*-unit.js' --reporter spec --require babel-register --require babel-polyfill testutils.js", 25 | "integration": "npm run integration-ws && npm run integration-node", 26 | "integration-node": "NODE_TLS_REJECT_UNAUTHORIZED=0 npx mocha './test/node/node-integration.js' --require testutils.js", 27 | "integration-ws": "npm run build && npx webpack --config webpack.config.test.js -p && npx wdio wdio.conf.js", 28 | "integration-chrome": "npm run build && cp node_modules/chai/chai.js node_modules/mocha/mocha.js node_modules/mocha/mocha.css test/chrome/ && npx webpack --config webpack.config.chrome.js && npx babel-node test/chrome/run.js" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/emailjs/emailjs-tcp-socket/issues" 32 | }, 33 | "homepage": "https://github.com/emailjs/emailjs-tcp-socket", 34 | "dependencies": { 35 | "node-forge": "^0.8.4", 36 | "ramda": "^0.26.1" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.26.0", 40 | "babel-core": "^6.26.3", 41 | "babel-loader": "^8.0.6", 42 | "babel-plugin-inline-import": "^3.0.0", 43 | "babel-polyfill": "^6.26.0", 44 | "babel-preset-env": "^1.7.0", 45 | "babel-register": "^6.26.0", 46 | "chai": "^4.2.0", 47 | "chrome-launcher": "^0.10.7", 48 | "chromedriver": "^74.0.0", 49 | "emailjs-tcp-proxy": "^1.0.2", 50 | "hoodiecrow-imap": "^2.1.0", 51 | "mocha": "^6.1.4", 52 | "pre-commit": "^1.2.2", 53 | "sinon": "^7.3.2", 54 | "standard": "^12.0.1", 55 | "wdio-chromedriver-service": "^5.0.1", 56 | "wdio-mocha-framework": "^0.6.4", 57 | "webdriverio": "^5.10.0", 58 | "webpack": "^4.33.0", 59 | "webpack-cli": "^3.3.3", 60 | "ws": "^7.0.0" 61 | }, 62 | "standard": { 63 | "globals": [ 64 | "describe", 65 | "it", 66 | "before", 67 | "beforeEach", 68 | "afterEach", 69 | "after", 70 | "expect", 71 | "sinon", 72 | "self", 73 | "Worker", 74 | "URL", 75 | "Blob", 76 | "chrome", 77 | "Windows", 78 | "postMessage", 79 | "io", 80 | "browser" 81 | ], 82 | "ignore": [ 83 | "dist" 84 | ] 85 | }, 86 | "pre-commit": [ 87 | "test", 88 | "integration-chrome" 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /src/socketio-socket-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import TCPSocket from './socketio-socket' 4 | 5 | describe('TcpSocket websocket unit tests', function () { 6 | var stubIo, socket 7 | 8 | var Io = function () { } 9 | Io.prototype.on = function () { } 10 | Io.prototype.emit = function () { } 11 | Io.prototype.disconnect = function () { } 12 | 13 | beforeEach(function (done) { 14 | stubIo = sinon.createStubInstance(Io) 15 | 16 | global.window = { 17 | location: { 18 | origin: 'hostname.io' 19 | } 20 | } 21 | global.io = function () { 22 | return stubIo 23 | } 24 | 25 | stubIo.emit.withArgs('open').yieldsAsync('hostname.io') 26 | 27 | socket = TCPSocket.open('127.0.0.1', 9000, { 28 | useSecureTransport: false, 29 | ca: '-----BEGIN CERTIFICATE-----\r\nMIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\r\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\r\nYWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG\r\nEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\r\nbmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\r\nAJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\r\nVaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\r\nh8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\r\nahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\r\nEASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\r\nDTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7\r\nqwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD\r\nVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g\r\nK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI\r\nKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n\r\nZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB\r\nBQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY\r\n/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/\r\nzG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza\r\nHFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\r\nWHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\r\nyuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\r\n-----END CERTIFICATE-----' 30 | }) 31 | expect(socket).to.exist 32 | expect(socket._ca).to.exist 33 | 34 | stubIo.on.withArgs('data').callsArgWithAsync(1, new Uint8Array([0, 1, 2]).buffer) 35 | socket.onopen = function (event) { 36 | expect(event.data.proxyHostname).to.equal('hostname.io') 37 | } 38 | socket.ondata = function (e) { 39 | expect(new Uint8Array(e.data)).to.deep.equal(new Uint8Array([0, 1, 2])) 40 | done() 41 | } 42 | }) 43 | 44 | describe('close', function () { 45 | it('should work', function (done) { 46 | socket.onclose = function () { 47 | expect(socket.readyState).to.equal('closed') 48 | expect(stubIo.disconnect.callCount).to.equal(1) 49 | expect(stubIo.emit.withArgs('end').callCount).to.equal(1) 50 | done() 51 | } 52 | 53 | socket.close() 54 | }) 55 | }) 56 | 57 | describe('send', function () { 58 | it('should not explode', function (done) { 59 | stubIo.emit.withArgs('data').callsArgWithAsync(2) 60 | 61 | socket.ondrain = function () { 62 | done() 63 | } 64 | 65 | socket.send(new Uint8Array([0, 1, 2]).buffer) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /src/windows-socket-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import TcpSocket from './windows-socket' 4 | 5 | describe('TCPSocket Windows Stream Socket unit tests', function () { 6 | var socket 7 | 8 | before(() => { 9 | global.Windows = { 10 | Networking: { 11 | HostName: function (hostname) { 12 | this.type = hostname 13 | this.hostname = hostname 14 | }, 15 | Sockets: { 16 | StreamSocket: function () { 17 | this.control = {} 18 | 19 | this.inputStream = { 20 | type: 'inputStream' 21 | } 22 | 23 | this.outputStream = { 24 | type: 'outputStream' 25 | } 26 | 27 | this.connectAsync = (host, port, protection) => { 28 | this.host = host 29 | this.port = port 30 | this.protection = protection 31 | return { 32 | done: successCb => setImmediate(successCb) 33 | } 34 | } 35 | }, 36 | SocketProtectionLevel: { 37 | plainSocket: 1, 38 | tls12: 2 39 | } 40 | } 41 | }, 42 | Storage: { 43 | Streams: { 44 | DataReader: function (stream) { 45 | this.type = 'DataReader' 46 | this.stream = stream 47 | this.inputStreamOptions = false 48 | 49 | this._bytes = false 50 | 51 | this.loadAsync = () => ({ 52 | done: successCb => setImmediate(() => successCb((this._bytes && this._bytes.length) || 0)) 53 | }) 54 | 55 | this.readBytes = target => { 56 | for (let i = 0, len = this._bytes.length; i < len; i++) { 57 | target[i] = this._bytes[i] 58 | } 59 | this._bytes = false 60 | } 61 | }, 62 | DataWriter: function (stream) { 63 | this.type = 'DataWriter' 64 | this.stream = stream 65 | this.inputStreamOptions = false 66 | 67 | this._bytes = false 68 | 69 | this.writeBytes = data => { this._bytes = data } 70 | 71 | this.storeAsync = () => ({ 72 | done: successCb => setImmediate(successCb) 73 | }) 74 | }, 75 | InputStreamOptions: { 76 | partial: 3 77 | } 78 | } 79 | } 80 | } 81 | }) 82 | 83 | beforeEach(function (done) { 84 | socket = TcpSocket.open('127.0.0.1', 9000, { 85 | useSecureTransport: false 86 | }) 87 | expect(socket).to.exist 88 | 89 | socket.onopen = function () { 90 | done() 91 | } 92 | }) 93 | 94 | describe('open and read', function () { 95 | it('should read data from socket', function (done) { 96 | socket.ondata = function (e) { 97 | expect(new Uint8Array(e.data)).to.deep.equal(new Uint8Array([0, 1, 2])) 98 | socket.close() 99 | } 100 | socket.onclose = () => done() 101 | 102 | socket._dataReader._bytes = new Uint8Array([0, 1, 2]) 103 | }) 104 | }) 105 | 106 | describe('close', function () { 107 | it('should work', function (done) { 108 | socket.onclose = function () { 109 | expect(socket.readyState).to.equal('closed') 110 | done() 111 | } 112 | 113 | socket.close() 114 | }) 115 | }) 116 | 117 | describe('send', function () { 118 | it('should send data to socket', function (done) { 119 | socket.ondrain = function () { 120 | socket.close() 121 | } 122 | socket.onclose = () => done() 123 | 124 | socket.send(new Uint8Array([0, 1, 2]).buffer) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /src/socketio-socket.js: -------------------------------------------------------------------------------- 1 | import { pathOr, propOr } from 'ramda' 2 | import createTls from './tls-utils' 3 | import { 4 | EVENT_INBOUND, EVENT_OUTBOUND, 5 | createMessage 6 | } from './worker-utils' 7 | 8 | export default class TCPSocket { 9 | static open (host, port, options = {}) { 10 | return new TCPSocket({ host, port, options }) 11 | } 12 | 13 | constructor ({ host, port, options }) { 14 | this.host = host 15 | this.port = port 16 | this.ssl = false 17 | this.bufferedAmount = 0 18 | this.readyState = 'connecting' 19 | this.binaryType = propOr('arraybuffer', 'binaryType')(options) 20 | 21 | if (this.binaryType !== 'arraybuffer') { 22 | throw new Error('Only arraybuffers are supported!') 23 | } 24 | 25 | this._ca = options.ca 26 | this._useTLS = propOr(false, 'useSecureTransport')(options) 27 | this._useSTARTTLS = false 28 | 29 | this._wsHost = pathOr(window.location.origin, ['ws', 'url'])(options) 30 | this._wsOptions = pathOr({}, ['ws', 'options'])(options) 31 | this._wsOptions.reconnection = this._wsOptions.reconnection || false 32 | this._wsOptions.multiplex = this._wsOptions.multiplex || false 33 | 34 | this._socket = io(this._wsHost, this._wsOptions) 35 | this._socket.emit('open', { host, port }, proxyHostname => { 36 | this._proxyHostname = proxyHostname 37 | if (this._useTLS) { 38 | // the socket is up, do the tls handshake 39 | createTls(this) 40 | } else { 41 | // socket is up and running 42 | this._emit('open', { 43 | proxyHostname: this._proxyHostname 44 | }) 45 | } 46 | 47 | this._socket.on('data', buffer => { 48 | if (this._useTLS || this._useSTARTTLS) { 49 | // feed the data to the tls socket 50 | if (this._tlsWorker) { 51 | this._tlsWorker.postMessage(createMessage(EVENT_INBOUND, buffer), [buffer]) 52 | } else { 53 | this._tls.processInbound(buffer) 54 | } 55 | } else { 56 | this._emit('data', buffer) 57 | } 58 | }) 59 | 60 | this._socket.on('error', message => { 61 | this._emit('error', new Error(message)) 62 | this.close() 63 | }) 64 | 65 | this._socket.on('close', () => this.close()) 66 | }) 67 | } 68 | 69 | close () { 70 | this.readyState = 'closing' 71 | 72 | this._socket.emit('end') 73 | this._socket.disconnect() 74 | 75 | if (this._tlsWorker) { 76 | this._tlsWorker.terminate() 77 | } 78 | 79 | this._emit('close') 80 | } 81 | 82 | send (buffer) { 83 | if (this._useTLS || this._useSTARTTLS) { 84 | // give buffer to forge to be prepared for tls 85 | if (this._tlsWorker) { 86 | this._tlsWorker.postMessage(createMessage(EVENT_OUTBOUND, buffer), [buffer]) 87 | } else { 88 | this._tls.prepareOutbound(buffer) 89 | } 90 | return 91 | } 92 | 93 | this._send(buffer) 94 | } 95 | 96 | _send (data) { 97 | this._socket.emit('data', data, () => this._emit('drain')) 98 | } 99 | 100 | upgradeToSecure () { 101 | if (this.ssl || this._useSTARTTLS) return 102 | 103 | this._useSTARTTLS = true 104 | createTls(this) 105 | } 106 | 107 | _emit (type, data) { 108 | const target = this 109 | switch (type) { 110 | case 'open': 111 | this.readyState = 'open' 112 | this.onopen && this.onopen({ target, type, data }) 113 | break 114 | case 'error': 115 | this.onerror && this.onerror({ target, type, data }) 116 | break 117 | case 'data': 118 | this.ondata && this.ondata({ target, type, data }) 119 | break 120 | case 'drain': 121 | this.ondrain && this.ondrain({ target, type, data }) 122 | break 123 | case 'close': 124 | this.readyState = 'closed' 125 | this.onclose && this.onclose({ target, type, data }) 126 | break 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /dist/socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 6 | 7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 8 | 9 | var TCPSocket = void 0; 10 | 11 | var DummySocket = function () { 12 | function DummySocket() { 13 | _classCallCheck(this, DummySocket); 14 | } 15 | 16 | _createClass(DummySocket, null, [{ 17 | key: 'open', 18 | value: function open() { 19 | throw new Error('Runtime does not offer raw sockets!'); 20 | } 21 | }]); 22 | 23 | return DummySocket; 24 | }(); 25 | 26 | if (typeof process !== 'undefined') { 27 | TCPSocket = require('./node-socket'); 28 | } else if (typeof chrome !== 'undefined' && (chrome.socket || chrome.sockets)) { 29 | TCPSocket = require('./chrome-socket'); 30 | } else if ((typeof Windows === 'undefined' ? 'undefined' : _typeof(Windows)) === 'object' && Windows && Windows.Networking && Windows.Networking.Sockets && Windows.Networking.Sockets.StreamSocket) { 31 | TCPSocket = require('./windows-socket'); 32 | } else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && typeof io === 'function') { 33 | TCPSocket = require('./socketio-socket'); 34 | } else { 35 | TCPSocket = DummySocket; 36 | } 37 | 38 | module.exports = TCPSocket; 39 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9zb2NrZXQuanMiXSwibmFtZXMiOlsiVENQU29ja2V0IiwiRHVtbXlTb2NrZXQiLCJFcnJvciIsInByb2Nlc3MiLCJyZXF1aXJlIiwiY2hyb21lIiwic29ja2V0Iiwic29ja2V0cyIsIldpbmRvd3MiLCJOZXR3b3JraW5nIiwiU29ja2V0cyIsIlN0cmVhbVNvY2tldCIsIndpbmRvdyIsImlvIiwibW9kdWxlIiwiZXhwb3J0cyJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFBQSxJQUFJQSxrQkFBSjs7SUFFTUMsVzs7Ozs7OzsyQkFDVztBQUNiLFlBQU0sSUFBSUMsS0FBSixDQUFVLHFDQUFWLENBQU47QUFDRDs7Ozs7O0FBR0gsSUFBSSxPQUFPQyxPQUFQLEtBQW1CLFdBQXZCLEVBQW9DO0FBQ2xDSCxjQUFZSSxRQUFRLGVBQVIsQ0FBWjtBQUNELENBRkQsTUFFTyxJQUFJLE9BQU9DLE1BQVAsS0FBa0IsV0FBbEIsS0FBa0NBLE9BQU9DLE1BQVAsSUFBaUJELE9BQU9FLE9BQTFELENBQUosRUFBd0U7QUFDN0VQLGNBQVlJLFFBQVEsaUJBQVIsQ0FBWjtBQUNELENBRk0sTUFFQSxJQUFJLFFBQU9JLE9BQVAseUNBQU9BLE9BQVAsT0FBbUIsUUFBbkIsSUFBK0JBLE9BQS9CLElBQTBDQSxRQUFRQyxVQUFsRCxJQUFnRUQsUUFBUUMsVUFBUixDQUFtQkMsT0FBbkYsSUFBOEZGLFFBQVFDLFVBQVIsQ0FBbUJDLE9BQW5CLENBQTJCQyxZQUE3SCxFQUEySTtBQUNoSlgsY0FBWUksUUFBUSxrQkFBUixDQUFaO0FBQ0QsQ0FGTSxNQUVBLElBQUksUUFBT1EsTUFBUCx5Q0FBT0EsTUFBUCxPQUFrQixRQUFsQixJQUE4QixPQUFPQyxFQUFQLEtBQWMsVUFBaEQsRUFBNEQ7QUFDakViLGNBQVlJLFFBQVEsbUJBQVIsQ0FBWjtBQUNELENBRk0sTUFFQTtBQUNMSixjQUFZQyxXQUFaO0FBQ0Q7O0FBRURhLE9BQU9DLE9BQVAsR0FBaUJmLFNBQWpCIiwiZmlsZSI6InNvY2tldC5qcyIsInNvdXJjZXNDb250ZW50IjpbImxldCBUQ1BTb2NrZXRcblxuY2xhc3MgRHVtbXlTb2NrZXQge1xuICBzdGF0aWMgb3BlbiAoKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdSdW50aW1lIGRvZXMgbm90IG9mZmVyIHJhdyBzb2NrZXRzIScpXG4gIH1cbn1cblxuaWYgKHR5cGVvZiBwcm9jZXNzICE9PSAndW5kZWZpbmVkJykge1xuICBUQ1BTb2NrZXQgPSByZXF1aXJlKCcuL25vZGUtc29ja2V0Jylcbn0gZWxzZSBpZiAodHlwZW9mIGNocm9tZSAhPT0gJ3VuZGVmaW5lZCcgJiYgKGNocm9tZS5zb2NrZXQgfHwgY2hyb21lLnNvY2tldHMpKSB7XG4gIFRDUFNvY2tldCA9IHJlcXVpcmUoJy4vY2hyb21lLXNvY2tldCcpXG59IGVsc2UgaWYgKHR5cGVvZiBXaW5kb3dzID09PSAnb2JqZWN0JyAmJiBXaW5kb3dzICYmIFdpbmRvd3MuTmV0d29ya2luZyAmJiBXaW5kb3dzLk5ldHdvcmtpbmcuU29ja2V0cyAmJiBXaW5kb3dzLk5ldHdvcmtpbmcuU29ja2V0cy5TdHJlYW1Tb2NrZXQpIHtcbiAgVENQU29ja2V0ID0gcmVxdWlyZSgnLi93aW5kb3dzLXNvY2tldCcpXG59IGVsc2UgaWYgKHR5cGVvZiB3aW5kb3cgPT09ICdvYmplY3QnICYmIHR5cGVvZiBpbyA9PT0gJ2Z1bmN0aW9uJykge1xuICBUQ1BTb2NrZXQgPSByZXF1aXJlKCcuL3NvY2tldGlvLXNvY2tldCcpXG59IGVsc2Uge1xuICBUQ1BTb2NrZXQgPSBEdW1teVNvY2tldFxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IFRDUFNvY2tldFxuIl19 -------------------------------------------------------------------------------- /dist/tls-worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _workerUtils = require('./worker-utils'); 4 | 5 | var _tls = require('./tls'); 6 | 7 | var _tls2 = _interopRequireDefault(_tls); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | var tls = new _tls2.default(); 12 | tls.tlserror = function (message) { 13 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_ERROR, message)); 14 | }; 15 | tls.tlscert = function (cert) { 16 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_CERT, cert)); 17 | }; 18 | tls.tlsclose = function () { 19 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_CLOSE)); 20 | }; 21 | tls.tlsopen = function () { 22 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_OPEN)); 23 | }; 24 | tls.tlsoutbound = function (buffer) { 25 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_OUTBOUND, buffer), [buffer]); 26 | }; 27 | tls.tlsinbound = function (buffer) { 28 | return self.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_INBOUND, buffer), [buffer]); 29 | }; 30 | 31 | self.onmessage = function (_ref) { 32 | var _ref$data = _ref.data, 33 | event = _ref$data.event, 34 | message = _ref$data.message; 35 | 36 | switch (event) { 37 | case _workerUtils.EVENT_INBOUND: 38 | tls.processInbound(message); 39 | break; 40 | case _workerUtils.EVENT_OUTBOUND: 41 | tls.prepareOutbound(message); 42 | break; 43 | case _workerUtils.EVENT_HANDSHAKE: 44 | tls.handshake(); 45 | break; 46 | case _workerUtils.EVENT_CONFIG: 47 | tls.configure(message); 48 | break; 49 | } 50 | }; 51 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy90bHMtd29ya2VyLmpzIl0sIm5hbWVzIjpbInRscyIsInRsc2Vycm9yIiwic2VsZiIsInBvc3RNZXNzYWdlIiwibWVzc2FnZSIsInRsc2NlcnQiLCJjZXJ0IiwidGxzY2xvc2UiLCJ0bHNvcGVuIiwidGxzb3V0Ym91bmQiLCJidWZmZXIiLCJ0bHNpbmJvdW5kIiwib25tZXNzYWdlIiwiZGF0YSIsImV2ZW50IiwicHJvY2Vzc0luYm91bmQiLCJwcmVwYXJlT3V0Ym91bmQiLCJoYW5kc2hha2UiLCJjb25maWd1cmUiXSwibWFwcGluZ3MiOiI7O0FBQUE7O0FBUUE7Ozs7OztBQUVBLElBQUlBLE1BQU0sbUJBQVY7QUFDQUEsSUFBSUMsUUFBSixHQUFlO0FBQUEsU0FBV0MsS0FBS0MsV0FBTCxDQUFpQiwwREFBMkJDLE9BQTNCLENBQWpCLENBQVg7QUFBQSxDQUFmO0FBQ0FKLElBQUlLLE9BQUosR0FBYztBQUFBLFNBQVFILEtBQUtDLFdBQUwsQ0FBaUIseURBQTBCRyxJQUExQixDQUFqQixDQUFSO0FBQUEsQ0FBZDtBQUNBTixJQUFJTyxRQUFKLEdBQWU7QUFBQSxTQUFNTCxLQUFLQyxXQUFMLENBQWlCLHlEQUFqQixDQUFOO0FBQUEsQ0FBZjtBQUNBSCxJQUFJUSxPQUFKLEdBQWM7QUFBQSxTQUFNTixLQUFLQyxXQUFMLENBQWlCLHdEQUFqQixDQUFOO0FBQUEsQ0FBZDtBQUNBSCxJQUFJUyxXQUFKLEdBQWtCO0FBQUEsU0FBVVAsS0FBS0MsV0FBTCxDQUFpQiw2REFBOEJPLE1BQTlCLENBQWpCLEVBQXdELENBQUNBLE1BQUQsQ0FBeEQsQ0FBVjtBQUFBLENBQWxCO0FBQ0FWLElBQUlXLFVBQUosR0FBaUI7QUFBQSxTQUFVVCxLQUFLQyxXQUFMLENBQWlCLDREQUE2Qk8sTUFBN0IsQ0FBakIsRUFBdUQsQ0FBQ0EsTUFBRCxDQUF2RCxDQUFWO0FBQUEsQ0FBakI7O0FBRUFSLEtBQUtVLFNBQUwsR0FBaUIsZ0JBQXdDO0FBQUEsdUJBQTVCQyxJQUE0QjtBQUFBLE1BQXBCQyxLQUFvQixhQUFwQkEsS0FBb0I7QUFBQSxNQUFiVixPQUFhLGFBQWJBLE9BQWE7O0FBQ3ZELFVBQVFVLEtBQVI7QUFDRTtBQUNFZCxVQUFJZSxjQUFKLENBQW1CWCxPQUFuQjtBQUNBO0FBQ0Y7QUFDRUosVUFBSWdCLGVBQUosQ0FBb0JaLE9BQXBCO0FBQ0E7QUFDRjtBQUNFSixVQUFJaUIsU0FBSjtBQUNBO0FBQ0Y7QUFDRWpCLFVBQUlrQixTQUFKLENBQWNkLE9BQWQ7QUFDQTtBQVpKO0FBY0QsQ0FmRCIsImZpbGUiOiJ0bHMtd29ya2VyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgRVZFTlRfSEFORFNIQUtFLFxuICBFVkVOVF9JTkJPVU5ELCBFVkVOVF9PVVRCT1VORCxcbiAgRVZFTlRfT1BFTiwgRVZFTlRfQ0xPU0UsXG4gIEVWRU5UX0NPTkZJRywgRVZFTlRfQ0VSVCxcbiAgRVZFTlRfRVJST1IsXG4gIGNyZWF0ZU1lc3NhZ2Vcbn0gZnJvbSAnLi93b3JrZXItdXRpbHMnXG5pbXBvcnQgVExTIGZyb20gJy4vdGxzJ1xuXG52YXIgdGxzID0gbmV3IFRMUygpXG50bHMudGxzZXJyb3IgPSBtZXNzYWdlID0+IHNlbGYucG9zdE1lc3NhZ2UoY3JlYXRlTWVzc2FnZShFVkVOVF9FUlJPUiwgbWVzc2FnZSkpXG50bHMudGxzY2VydCA9IGNlcnQgPT4gc2VsZi5wb3N0TWVzc2FnZShjcmVhdGVNZXNzYWdlKEVWRU5UX0NFUlQsIGNlcnQpKVxudGxzLnRsc2Nsb3NlID0gKCkgPT4gc2VsZi5wb3N0TWVzc2FnZShjcmVhdGVNZXNzYWdlKEVWRU5UX0NMT1NFKSlcbnRscy50bHNvcGVuID0gKCkgPT4gc2VsZi5wb3N0TWVzc2FnZShjcmVhdGVNZXNzYWdlKEVWRU5UX09QRU4pKVxudGxzLnRsc291dGJvdW5kID0gYnVmZmVyID0+IHNlbGYucG9zdE1lc3NhZ2UoY3JlYXRlTWVzc2FnZShFVkVOVF9PVVRCT1VORCwgYnVmZmVyKSwgW2J1ZmZlcl0pXG50bHMudGxzaW5ib3VuZCA9IGJ1ZmZlciA9PiBzZWxmLnBvc3RNZXNzYWdlKGNyZWF0ZU1lc3NhZ2UoRVZFTlRfSU5CT1VORCwgYnVmZmVyKSwgW2J1ZmZlcl0pXG5cbnNlbGYub25tZXNzYWdlID0gZnVuY3Rpb24gKHsgZGF0YTogeyBldmVudCwgbWVzc2FnZSB9IH0pIHtcbiAgc3dpdGNoIChldmVudCkge1xuICAgIGNhc2UgRVZFTlRfSU5CT1VORDpcbiAgICAgIHRscy5wcm9jZXNzSW5ib3VuZChtZXNzYWdlKVxuICAgICAgYnJlYWtcbiAgICBjYXNlIEVWRU5UX09VVEJPVU5EOlxuICAgICAgdGxzLnByZXBhcmVPdXRib3VuZChtZXNzYWdlKVxuICAgICAgYnJlYWtcbiAgICBjYXNlIEVWRU5UX0hBTkRTSEFLRTpcbiAgICAgIHRscy5oYW5kc2hha2UoKVxuICAgICAgYnJlYWtcbiAgICBjYXNlIEVWRU5UX0NPTkZJRzpcbiAgICAgIHRscy5jb25maWd1cmUobWVzc2FnZSlcbiAgICAgIGJyZWFrXG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /dist/timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _timers = require('core-js/library/web/timers'); 8 | 9 | // setZeroTimeout slightly adapted from 10 | // https://github.com/shahyar/setZeroTimeout-js (CC BY 3.0). 11 | // Provides a function similar to setImmediate() on Chrome. 12 | var timeouts = []; 13 | var msgName = 'hackyVersionOfSetImmediate'; 14 | 15 | function postTimeout(fn) { 16 | timeouts.push(fn); 17 | postMessage(msgName, '*'); 18 | } 19 | function handleMessage(event) { 20 | if (event.source === window && event.data === msgName) { 21 | if (event.stopPropagation) { 22 | event.stopPropagation(); 23 | } 24 | if (timeouts.length) { 25 | try { 26 | timeouts.shift()(); 27 | } catch (e) { 28 | // Throw in an asynchronous closure to prevent setZeroTimeout from hanging due to error 29 | (0, _timers.setTimeout)(function (e) { 30 | return function () { 31 | throw e.stack || e; 32 | }; 33 | }(e), 0); 34 | } 35 | } 36 | if (timeouts.length) { 37 | // more left? 38 | postMessage(msgName, '*'); 39 | } 40 | } 41 | } 42 | 43 | var fn = void 0; 44 | if (typeof setImmediate !== 'undefined') { 45 | fn = setImmediate; 46 | } else if (typeof window !== 'undefined') { 47 | window.addEventListener('message', handleMessage, true); 48 | fn = postTimeout; 49 | } else { 50 | fn = function fn(f) { 51 | return (0, _timers.setTimeout)(f, 0); 52 | }; 53 | } 54 | 55 | exports.default = fn; 56 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy90aW1lb3V0LmpzIl0sIm5hbWVzIjpbInRpbWVvdXRzIiwibXNnTmFtZSIsInBvc3RUaW1lb3V0IiwiZm4iLCJwdXNoIiwicG9zdE1lc3NhZ2UiLCJoYW5kbGVNZXNzYWdlIiwiZXZlbnQiLCJzb3VyY2UiLCJ3aW5kb3ciLCJkYXRhIiwic3RvcFByb3BhZ2F0aW9uIiwibGVuZ3RoIiwic2hpZnQiLCJlIiwic3RhY2siLCJzZXRJbW1lZGlhdGUiLCJhZGRFdmVudExpc3RlbmVyIiwiZiJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsSUFBTUEsV0FBVyxFQUFqQjtBQUNBLElBQU1DLFVBQVUsNEJBQWhCOztBQUVBLFNBQVNDLFdBQVQsQ0FBc0JDLEVBQXRCLEVBQTBCO0FBQ3hCSCxXQUFTSSxJQUFULENBQWNELEVBQWQ7QUFDQUUsY0FBWUosT0FBWixFQUFxQixHQUFyQjtBQUNEO0FBQ0QsU0FBU0ssYUFBVCxDQUF3QkMsS0FBeEIsRUFBK0I7QUFDN0IsTUFBSUEsTUFBTUMsTUFBTixLQUFpQkMsTUFBakIsSUFBMkJGLE1BQU1HLElBQU4sS0FBZVQsT0FBOUMsRUFBdUQ7QUFDckQsUUFBSU0sTUFBTUksZUFBVixFQUEyQjtBQUN6QkosWUFBTUksZUFBTjtBQUNEO0FBQ0QsUUFBSVgsU0FBU1ksTUFBYixFQUFxQjtBQUNuQixVQUFJO0FBQ0ZaLGlCQUFTYSxLQUFUO0FBQ0QsT0FGRCxDQUVFLE9BQU9DLENBQVAsRUFBVTtBQUNWO0FBQ0EsZ0NBQVksVUFBVUEsQ0FBVixFQUFhO0FBQ3ZCLGlCQUFPLFlBQVk7QUFDakIsa0JBQU1BLEVBQUVDLEtBQUYsSUFBV0QsQ0FBakI7QUFDRCxXQUZEO0FBR0QsU0FKVyxDQUlWQSxDQUpVLENBQVosRUFJTyxDQUpQO0FBS0Q7QUFDRjtBQUNELFFBQUlkLFNBQVNZLE1BQWIsRUFBcUI7QUFBRTtBQUNyQlAsa0JBQVlKLE9BQVosRUFBcUIsR0FBckI7QUFDRDtBQUNGO0FBQ0Y7O0FBRUQsSUFBSUUsV0FBSjtBQUNBLElBQUksT0FBT2EsWUFBUCxLQUF3QixXQUE1QixFQUF5QztBQUN2Q2IsT0FBS2EsWUFBTDtBQUNELENBRkQsTUFFTyxJQUFJLE9BQU9QLE1BQVAsS0FBa0IsV0FBdEIsRUFBbUM7QUFDeENBLFNBQU9RLGdCQUFQLENBQXdCLFNBQXhCLEVBQW1DWCxhQUFuQyxFQUFrRCxJQUFsRDtBQUNBSCxPQUFLRCxXQUFMO0FBQ0QsQ0FITSxNQUdBO0FBQ0xDLE9BQUs7QUFBQSxXQUFLLHdCQUFXZSxDQUFYLEVBQWMsQ0FBZCxDQUFMO0FBQUEsR0FBTDtBQUNEOztrQkFFY2YsRSIsImZpbGUiOiJ0aW1lb3V0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc2V0VGltZW91dCB9IGZyb20gJ2NvcmUtanMvbGlicmFyeS93ZWIvdGltZXJzJ1xuXG4vLyBzZXRaZXJvVGltZW91dCBzbGlnaHRseSBhZGFwdGVkIGZyb21cbi8vIGh0dHBzOi8vZ2l0aHViLmNvbS9zaGFoeWFyL3NldFplcm9UaW1lb3V0LWpzIChDQyBCWSAzLjApLlxuLy8gUHJvdmlkZXMgYSBmdW5jdGlvbiBzaW1pbGFyIHRvIHNldEltbWVkaWF0ZSgpIG9uIENocm9tZS5cbmNvbnN0IHRpbWVvdXRzID0gW11cbmNvbnN0IG1zZ05hbWUgPSAnaGFja3lWZXJzaW9uT2ZTZXRJbW1lZGlhdGUnXG5cbmZ1bmN0aW9uIHBvc3RUaW1lb3V0IChmbikge1xuICB0aW1lb3V0cy5wdXNoKGZuKVxuICBwb3N0TWVzc2FnZShtc2dOYW1lLCAnKicpXG59XG5mdW5jdGlvbiBoYW5kbGVNZXNzYWdlIChldmVudCkge1xuICBpZiAoZXZlbnQuc291cmNlID09PSB3aW5kb3cgJiYgZXZlbnQuZGF0YSA9PT0gbXNnTmFtZSkge1xuICAgIGlmIChldmVudC5zdG9wUHJvcGFnYXRpb24pIHtcbiAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXG4gICAgfVxuICAgIGlmICh0aW1lb3V0cy5sZW5ndGgpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHRpbWVvdXRzLnNoaWZ0KCkoKVxuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAvLyBUaHJvdyBpbiBhbiBhc3luY2hyb25vdXMgY2xvc3VyZSB0byBwcmV2ZW50IHNldFplcm9UaW1lb3V0IGZyb20gaGFuZ2luZyBkdWUgdG8gZXJyb3JcbiAgICAgICAgc2V0VGltZW91dCgoZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdGhyb3cgZS5zdGFjayB8fCBlXG4gICAgICAgICAgfVxuICAgICAgICB9KGUpKSwgMClcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRpbWVvdXRzLmxlbmd0aCkgeyAvLyBtb3JlIGxlZnQ/XG4gICAgICBwb3N0TWVzc2FnZShtc2dOYW1lLCAnKicpXG4gICAgfVxuICB9XG59XG5cbmxldCBmblxuaWYgKHR5cGVvZiBzZXRJbW1lZGlhdGUgIT09ICd1bmRlZmluZWQnKSB7XG4gIGZuID0gc2V0SW1tZWRpYXRlXG59IGVsc2UgaWYgKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnKSB7XG4gIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlTWVzc2FnZSwgdHJ1ZSlcbiAgZm4gPSBwb3N0VGltZW91dFxufSBlbHNlIHtcbiAgZm4gPSBmID0+IHNldFRpbWVvdXQoZiwgMClcbn1cblxuZXhwb3J0IGRlZmF1bHQgZm5cbiJdfQ== -------------------------------------------------------------------------------- /test/starttls.js: -------------------------------------------------------------------------------- 1 | // 2 | // StartTLS implementation by Haraka 3 | // https://github.com/haraka/Haraka/blob/master/tls_socket.js 4 | // 5 | 6 | /* -------------------------------------------------------------------------- */ 7 | /* Obtained and modified from http://js.5sh.net/starttls.js on 8/18/2011. */ 8 | /* -------------------------------------------------------------------------- */ 9 | 10 | const tls = require('tls') 11 | const net = require('net') 12 | const stream = require('stream') 13 | 14 | const certsByHost = {} 15 | 16 | // provides a common socket for attaching 17 | // and detaching from either main socket, or crypto socket 18 | class PluggableStream extends stream.Stream { 19 | constructor (socket) { 20 | super() 21 | this.readable = this.writable = true 22 | this._timeout = 0 23 | this._keepalive = false 24 | this._writeState = true 25 | this._pending = [] 26 | this._pendingCallbacks = [] 27 | if (socket) this.attach(socket) 28 | } 29 | 30 | pause () { 31 | if (this.targetsocket.pause) { 32 | this.targetsocket.pause() 33 | this.readable = false 34 | } 35 | } 36 | 37 | resume () { 38 | if (this.targetsocket.resume) { 39 | this.readable = true 40 | this.targetsocket.resume() 41 | } 42 | } 43 | 44 | attach (socket) { 45 | const self = this 46 | self.targetsocket = socket 47 | self.targetsocket.on('data', function (data) { 48 | self.emit('data', data) 49 | }) 50 | self.targetsocket.on('connect', (a, b) => { 51 | self.emit('connect', a, b) 52 | }) 53 | self.targetsocket.on('secureConnection', function (a, b) { 54 | self.emit('secureConnection', a, b) 55 | self.emit('secure', a, b) 56 | }) 57 | self.targetsocket.on('secure', function (a, b) { 58 | self.emit('secureConnection', a, b) 59 | self.emit('secure', a, b) 60 | }) 61 | self.targetsocket.on('end', function () { 62 | self.writable = self.targetsocket.writable 63 | self.emit('end') 64 | }) 65 | self.targetsocket.on('close', function (hadError) { 66 | self.writable = self.targetsocket.writable 67 | self.emit('close', hadError) 68 | }) 69 | self.targetsocket.on('drain', function () { 70 | self.emit('drain') 71 | }) 72 | self.targetsocket.once('error', function (exception) { 73 | self.writable = self.targetsocket.writable 74 | self.emit('error', exception) 75 | }) 76 | self.targetsocket.on('timeout', function () { 77 | self.emit('timeout') 78 | }) 79 | if (self.targetsocket.remotePort) { 80 | self.remotePort = self.targetsocket.remotePort 81 | } 82 | if (self.targetsocket.remoteAddress) { 83 | self.remoteAddress = self.targetsocket.remoteAddress 84 | } 85 | } 86 | 87 | clean (data) { 88 | if (this.targetsocket && this.targetsocket.removeAllListeners) { 89 | this.targetsocket.removeAllListeners('data') 90 | this.targetsocket.removeAllListeners('secureConnection') 91 | this.targetsocket.removeAllListeners('secure') 92 | this.targetsocket.removeAllListeners('end') 93 | this.targetsocket.removeAllListeners('close') 94 | this.targetsocket.removeAllListeners('error') 95 | this.targetsocket.removeAllListeners('drain') 96 | } 97 | this.targetsocket = {} 98 | this.targetsocket.write = function () { } 99 | } 100 | 101 | write (data, encoding, callback) { 102 | if (this.targetsocket.write) { 103 | return this.targetsocket.write(data, encoding, callback) 104 | } 105 | return false 106 | } 107 | 108 | end (data, encoding) { 109 | if (this.targetsocket.end) { 110 | return this.targetsocket.end(data, encoding) 111 | } 112 | } 113 | 114 | destroySoon () { 115 | if (this.targetsocket.destroySoon) { 116 | return this.targetsocket.destroySoon() 117 | } 118 | } 119 | 120 | destroy () { 121 | if (this.targetsocket.destroy) { 122 | return this.targetsocket.destroy() 123 | } 124 | } 125 | 126 | setKeepAlive (bool) { 127 | this._keepalive = bool 128 | return this.targetsocket.setKeepAlive(bool) 129 | } 130 | 131 | setNoDelay (/* true||false */) { 132 | } 133 | 134 | unref () { 135 | return this.targetsocket.unref() 136 | } 137 | 138 | setTimeout (timeout) { 139 | this._timeout = timeout 140 | return this.targetsocket.setTimeout(timeout) 141 | } 142 | } 143 | 144 | function pipe (cleartext, socket) { 145 | cleartext.socket = socket 146 | 147 | const onerror = e => { 148 | } 149 | 150 | function onclose () { 151 | socket.removeListener('error', onerror) 152 | socket.removeListener('close', onclose) 153 | } 154 | 155 | socket.on('error', onerror) 156 | socket.on('close', onclose) 157 | } 158 | 159 | function createServer (cb) { 160 | const server = net.createServer(cryptoSocket => { 161 | const socket = new PluggableStream(cryptoSocket) 162 | 163 | socket.upgrade = cb2 => { 164 | socket.clean() 165 | 166 | cryptoSocket.removeAllListeners('data') 167 | 168 | const options = Object.assign({}, certsByHost['*']) 169 | options.server = server // TLSSocket needs server for SNI to work 170 | 171 | const cleartext = new tls.TLSSocket(cryptoSocket, options) 172 | 173 | pipe(cleartext, cryptoSocket) 174 | 175 | cleartext 176 | .on('error', exception => { 177 | socket.emit('error', exception) 178 | }) 179 | .on('secure', () => { 180 | socket.emit('secure') 181 | if (cb2) { 182 | cb2( 183 | cleartext.authorized, 184 | cleartext.authorizationError, 185 | cleartext.getPeerCertificate(), 186 | cleartext.getCipher() 187 | ) 188 | } 189 | }) 190 | 191 | socket.cleartext = cleartext 192 | 193 | if (socket._timeout) { 194 | cleartext.setTimeout(socket._timeout) 195 | } 196 | 197 | cleartext.setKeepAlive(socket._keepalive) 198 | 199 | socket.attach(socket.cleartext) 200 | } 201 | 202 | cb(socket) 203 | }) 204 | 205 | return server 206 | } 207 | 208 | exports.createServer = createServer 209 | -------------------------------------------------------------------------------- /src/tls.js: -------------------------------------------------------------------------------- 1 | import { tls, pki } from 'node-forge' 2 | 3 | export default class TlsClient { 4 | constructor () { 5 | this.open = false 6 | this._outboundBuffer = [] 7 | 8 | this._tls = tls.createConnection({ 9 | server: false, 10 | verify: (connection, verified, depth, certs) => { 11 | if (!(certs && certs[0])) { 12 | return false 13 | } 14 | 15 | if (!this.verifyCertificate(certs[0], this._host)) { 16 | return false 17 | } 18 | 19 | /* 20 | * Please see the readme for an explanation of the behavior without a native TLS stack! 21 | */ 22 | 23 | // without a pinned certificate, we'll just accept the connection and notify the upper layer 24 | if (!this._ca) { 25 | // notify the upper layer of the new cert 26 | this.tlscert(pki.certificateToPem(certs[0])) 27 | // succeed only if this.tlscert is implemented (otherwise forge catches the error) 28 | return true 29 | } 30 | 31 | // if we have a pinned certificate, things get a little more complicated: 32 | // - leaf certificates pin the host directly, e.g. for self-signed certificates 33 | // - we also allow intermediate certificates, for providers that are able to sign their own certs. 34 | 35 | // detect if this is a certificate used for signing by testing if the common name different from the hostname. 36 | // also, an intermediate cert has no SANs, at least none that match the hostname. 37 | if (!this.verifyCertificate(this._ca, this._host)) { 38 | // verify certificate through a valid certificate chain 39 | return this._ca.verify(certs[0]) 40 | } 41 | 42 | // verify certificate through host certificate pinning 43 | var fpPinned = pki.getPublicKeyFingerprint(this._ca.publicKey, { 44 | encoding: 'hex' 45 | }) 46 | var fpRemote = pki.getPublicKeyFingerprint(certs[0].publicKey, { 47 | encoding: 'hex' 48 | }) 49 | 50 | // check if cert fingerprints match 51 | if (fpPinned === fpRemote) { 52 | return true 53 | } 54 | 55 | // notify the upper layer of the new cert 56 | this.tlscert(pki.certificateToPem(certs[0])) 57 | // fail when fingerprint does not match 58 | return false 59 | }, 60 | connected: (connection) => { 61 | if (!connection) { 62 | this.tlserror('Unable to connect') 63 | this.tlsclose() 64 | return 65 | } 66 | 67 | // tls connection open 68 | this.open = true 69 | 70 | this.tlsopen() 71 | 72 | // empty the buffer 73 | while (this._outboundBuffer.length) { 74 | this.prepareOutbound(this._outboundBuffer.shift()) 75 | } 76 | }, 77 | tlsDataReady: (connection) => this.tlsoutbound(s2a(connection.tlsData.getBytes())), 78 | dataReady: (connection) => this.tlsinbound(s2a(connection.data.getBytes())), 79 | closed: () => this.tlsclose(), 80 | error: (connection, error) => { 81 | this.tlserror(error.message) 82 | this.tlsclose() 83 | } 84 | }) 85 | } 86 | 87 | configure (options) { 88 | this._host = options.host 89 | if (options.ca) { 90 | this._ca = pki.certificateFromPem(options.ca) 91 | } 92 | } 93 | 94 | prepareOutbound (buffer) { 95 | if (!this.open) { 96 | this._outboundBuffer.push(buffer) 97 | return 98 | } 99 | 100 | this._tls.prepare(a2s(buffer)) 101 | } 102 | 103 | processInbound (buffer) { 104 | this._tls.process(a2s(buffer)) 105 | } 106 | 107 | handshake () { 108 | this._tls.handshake() 109 | } 110 | 111 | /** 112 | * Verifies a host name by the Common Name or Subject Alternative Names 113 | * Expose as a method of TlsClient for testing purposes 114 | * 115 | * @param {Object} cert A forge certificate object 116 | * @param {String} host The host name, e.g. imap.gmail.com 117 | * @return {Boolean} true, if host name matches certificate, otherwise false 118 | */ 119 | verifyCertificate (cert, host) { 120 | let entries 121 | 122 | const subjectAltName = cert.getExtension({ 123 | name: 'subjectAltName' 124 | }) 125 | 126 | const cn = cert.subject.getField('CN') 127 | 128 | // If subjectAltName is present then it must be used and Common Name must be discarded 129 | // http://tools.ietf.org/html/rfc2818#section-3.1 130 | // So we check subjectAltName first and if it does not exist then revert back to Common Name 131 | if (subjectAltName && subjectAltName.altNames && subjectAltName.altNames.length) { 132 | entries = subjectAltName.altNames.map(function (entry) { 133 | return entry.value 134 | }) 135 | } else if (cn && cn.value) { 136 | entries = [cn.value] 137 | } else { 138 | return false 139 | } 140 | 141 | // find matches for hostname and if any are found return true, otherwise returns false 142 | return !!entries.filter(sanEntry => this.compareServername(host.toLowerCase(), sanEntry.toLowerCase())).length 143 | } 144 | 145 | /** 146 | * Compares servername with a subjectAltName entry. Returns true if these values match. 147 | * 148 | * Wildcard usage in certificate hostnames is very limited, the only valid usage 149 | * form is "*.domain" and not "*sub.domain" or "sub.*.domain" so we only have to check 150 | * if the entry starts with "*." when comparing against a wildcard hostname. If "*" is used 151 | * in invalid places, then treat it as a string and not as a wildcard. 152 | * 153 | * @param {String} servername Hostname to check 154 | * @param {String} sanEntry subjectAltName entry to check against 155 | * @returns {Boolean} Returns true if hostname matches entry from SAN 156 | */ 157 | compareServername (servername = '', sanEntry = '') { 158 | // if the entry name does not include a wildcard, then expect exact match 159 | if (sanEntry.substr(0, 2) !== '*.') { 160 | return sanEntry === servername 161 | } 162 | 163 | // otherwise ignore the first subdomain 164 | return servername.split('.').slice(1).join('.') === sanEntry.substr(2) 165 | } 166 | } 167 | 168 | const a2s = arr => String.fromCharCode.apply(null, new Uint8Array(arr)) 169 | const s2a = str => new Uint8Array(str.split('').map(char => char.charCodeAt(0))).buffer 170 | -------------------------------------------------------------------------------- /src/windows-socket.js: -------------------------------------------------------------------------------- 1 | import { propOr } from 'ramda' 2 | 3 | export default class TCPSocket { 4 | static open (host, port, options = {}) { 5 | return new TCPSocket({ host, port, options }) 6 | } 7 | 8 | constructor ({ host, port, options }) { 9 | this.host = new Windows.Networking.HostName(host) // NB! HostName constructor will throw on invalid input 10 | this.port = port 11 | this.ssl = propOr(false, 'useSecureTransport')(options) 12 | this.bufferedAmount = 0 13 | this.readyState = 'connecting' 14 | this.binaryType = propOr('arraybuffer', 'binaryType')(options) 15 | 16 | if (this.binaryType !== 'arraybuffer') { 17 | throw new Error('Only arraybuffers are supported!') 18 | } 19 | 20 | this._socket = new Windows.Networking.Sockets.StreamSocket() 21 | 22 | this._socket.control.keepAlive = true 23 | this._socket.control.noDelay = true 24 | 25 | this._dataReader = null 26 | this._dataWriter = null 27 | 28 | // set to true if upgrading with STARTTLS 29 | this._upgrading = false 30 | 31 | // cache all client.send calls to this array if currently upgrading 32 | this._upgradeCache = [] 33 | 34 | // initial socket type. default is 'plainSocket' (no encryption applied) 35 | // 'tls12' supports the TLS 1.2, TLS 1.1 and TLS 1.0 protocols but no SSL 36 | this._protectionLevel = Windows.Networking.Sockets.SocketProtectionLevel[this.ssl ? 'tls12' : 'plainSocket'] 37 | 38 | // Initiate connection to destination 39 | this._socket 40 | .connectAsync(this.host, this.port, this._protectionLevel) 41 | .done(() => { 42 | this._setStreamHandlers() 43 | this._emit('open') 44 | }, e => this._emit('error', e)) 45 | } 46 | 47 | /** 48 | * Initiate Reader and Writer interfaces for the socket 49 | */ 50 | _setStreamHandlers () { 51 | this._dataReader = new Windows.Storage.Streams.DataReader(this._socket.inputStream) 52 | this._dataReader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial 53 | 54 | // setup writer 55 | this._dataWriter = new Windows.Storage.Streams.DataWriter(this._socket.outputStream) 56 | 57 | // start byte reader loop 58 | this._read() 59 | } 60 | 61 | /** 62 | * Emit an error and close socket 63 | * 64 | * @param {Error} error Error object 65 | */ 66 | _errorHandler (error) { 67 | // we ignore errors after close has been called, since all aborted operations 68 | // will emit their error handlers 69 | // this will also apply to starttls as a read call is aborted before upgrading the socket 70 | if (this._upgrading || (this.readyState !== 'closing' && this.readyState !== 'closed')) { 71 | this._emit('error', error) 72 | this.close() 73 | } 74 | } 75 | 76 | /** 77 | * Read available bytes from the socket. This method is recursive once it ends, it restarts itthis 78 | */ 79 | _read () { 80 | if (this._upgrading || (this.readyState !== 'open' && this.readyState !== 'connecting')) { 81 | return // do nothing if socket not open 82 | } 83 | 84 | // Read up to 4096 bytes from the socket. This is not a fixed number (the mode was set 85 | // with inputStreamOptions.partial property), so it might return with a smaller 86 | // amount of bytes. 87 | this._dataReader.loadAsync(4096).done(availableByteCount => { 88 | if (!availableByteCount) { 89 | // no bytes available for reading, restart the reading process 90 | return setImmediate(this._read.bind(this)) 91 | } 92 | 93 | // we need an Uint8Array that gets filled with the bytes from the buffer 94 | var data = new Uint8Array(availableByteCount) 95 | this._dataReader.readBytes(data) // data argument gets filled with the bytes 96 | 97 | this._emit('data', data.buffer) 98 | 99 | // restart reading process 100 | return setImmediate(this._read.bind(this)) 101 | }, e => this._errorHandler(e)) 102 | } 103 | 104 | // 105 | // API 106 | // 107 | 108 | close () { 109 | this.readyState = 'closing' 110 | 111 | try { 112 | this._socket.close() 113 | } catch (E) { 114 | this._emit('error', E) 115 | } 116 | 117 | setImmediate(this._emit.bind(this, 'close')) 118 | } 119 | 120 | send (data) { 121 | if (this.readyState !== 'open') { 122 | return 123 | } 124 | 125 | if (this._upgrading) { 126 | this._upgradeCache.push(data) 127 | return 128 | } 129 | 130 | // Write bytes to buffer 131 | this._dataWriter.writeBytes(data) 132 | 133 | // Emit buffer contents 134 | this._dataWriter.storeAsync().done(() => this._emit('drain'), (e) => this._errorHandler(e)) 135 | } 136 | 137 | upgradeToSecure () { 138 | if (this.ssl || this._upgrading) return 139 | 140 | this._upgrading = true 141 | try { 142 | // release current input stream. this is required to allow socket upgrade 143 | // write stream is not released as all send calls are cached from this point onwards 144 | // and not passed to socket until the socket is upgraded 145 | this._dataReader.detachStream() 146 | } catch (E) { } 147 | 148 | // update protection level 149 | this._protectionLevel = Windows.Networking.Sockets.SocketProtectionLevel.tls12 150 | 151 | this._socket.upgradeToSslAsync(this._protectionLevel, this.host).done( 152 | () => { 153 | this._upgrading = false 154 | this.ssl = true // secured connection from now on 155 | 156 | this._dataReader = new Windows.Storage.Streams.DataReader(this._socket.inputStream) 157 | this._dataReader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial 158 | this._read() 159 | 160 | // emit all cached requests 161 | while (this._upgradeCache.length) { 162 | const data = this._upgradeCache.shift() 163 | this.send(data) 164 | } 165 | }, 166 | (e) => { 167 | this._upgrading = false 168 | this._errorHandler(e) 169 | } 170 | ) 171 | } 172 | 173 | _emit (type, data) { 174 | const target = this 175 | switch (type) { 176 | case 'open': 177 | this.readyState = 'open' 178 | this.onopen && this.onopen({ target, type, data }) 179 | break 180 | case 'error': 181 | this.onerror && this.onerror({ target, type, data }) 182 | break 183 | case 'data': 184 | this.ondata && this.ondata({ target, type, data }) 185 | break 186 | case 'drain': 187 | this.ondrain && this.ondrain({ target, type, data }) 188 | break 189 | case 'close': 190 | this.readyState = 'closed' 191 | this.onclose && this.onclose({ target, type, data }) 192 | break 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/chrome-socket-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import TCPSocket from './chrome-socket' 4 | 5 | describe('TcpSocket Chrome Socket unit tests', function () { 6 | let socket 7 | let socketStub 8 | let testData = new Uint8Array([0, 1, 2]) 9 | 10 | before(() => { 11 | global.chrome = {} 12 | }) 13 | 14 | describe('chrome.socket', function () { 15 | beforeEach(function () { 16 | // create chrome.socket stub 17 | var ChromeLegacySocket = function () { } 18 | ChromeLegacySocket.prototype.create = function () { } 19 | ChromeLegacySocket.prototype.connect = function () { } 20 | ChromeLegacySocket.prototype.read = function () { } 21 | ChromeLegacySocket.prototype.disconnect = function () { } 22 | ChromeLegacySocket.prototype.destroy = function () { } 23 | ChromeLegacySocket.prototype.write = function () { } 24 | ChromeLegacySocket.prototype.secure = function () { } 25 | 26 | chrome.socket = socketStub = sinon.createStubInstance(ChromeLegacySocket) 27 | chrome.sockets = undefined 28 | chrome.runtime = { 29 | getPlatformInfo: fn => { fn({ os: 'mac' }) } 30 | } 31 | 32 | socketStub.create.withArgs('tcp').yields({ socketId: 42 }) 33 | socketStub.connect.withArgs(42, '127.0.0.1', 9000).yieldsAsync(0) 34 | socketStub.secure.withArgs(42).yieldsAsync(0) 35 | socketStub.read.withArgs(42).yieldsAsync({ resultCode: 1, data: testData.buffer }) 36 | socketStub.write.withArgs(42).yieldsAsync({ bytesWritten: 3 }) 37 | }) 38 | 39 | it('should open, read, write, close without ssl', function (done) { 40 | var sent = false 41 | 42 | socket = TCPSocket.open('127.0.0.1', 9000, { 43 | useSecureTransport: false 44 | }) 45 | 46 | socket.onopen = function () { 47 | expect(socket._socketId).to.equal(42) 48 | expect(socket.ssl).to.be.false 49 | } 50 | 51 | socket.ondata = function (e) { 52 | var buf = new Uint8Array(e.data) 53 | expect(buf).to.deep.equal(testData) 54 | 55 | if (!sent) { 56 | sent = !sent 57 | socket.send(new Uint8Array([0, 1, 2]).buffer) 58 | } 59 | } 60 | 61 | socket.ondrain = function () { 62 | socket.close() 63 | } 64 | 65 | socket.onclose = function () { 66 | expect(socket.readyState).to.equal('closed') 67 | expect(socket._socketId).to.equal(0) 68 | expect(socketStub.create.calledOnce).to.be.true 69 | expect(socketStub.connect.calledOnce).to.be.true 70 | expect(socketStub.secure.called).to.be.false 71 | expect(socketStub.read.called).to.be.true 72 | expect(socketStub.disconnect.calledOnce).to.be.true 73 | expect(socketStub.destroy.calledOnce).to.be.true 74 | 75 | done() 76 | } 77 | }) 78 | 79 | it('should open, read, write, close with ssl', function (done) { 80 | var sent = false 81 | 82 | socket = TCPSocket.open('127.0.0.1', 9000, { 83 | useSecureTransport: true 84 | }) 85 | 86 | socket.onopen = function () { 87 | expect(socket._socketId).to.equal(42) 88 | expect(socket.ssl).to.be.true 89 | } 90 | 91 | socket.ondata = function (e) { 92 | var buf = new Uint8Array(e.data) 93 | expect(buf).to.deep.equal(testData) 94 | 95 | if (!sent) { 96 | sent = !sent 97 | socket.send(new Uint8Array([0, 1, 2]).buffer) 98 | } 99 | } 100 | 101 | socket.ondrain = function () { 102 | socket.close() 103 | } 104 | 105 | socket.onclose = function () { 106 | expect(socket.readyState).to.equal('closed') 107 | expect(socket._socketId).to.equal(0) 108 | expect(socketStub.create.calledOnce).to.be.true 109 | expect(socketStub.connect.calledOnce).to.be.true 110 | expect(socketStub.secure.calledOnce).to.be.true 111 | expect(socketStub.read.called).to.be.true 112 | expect(socketStub.write.called).to.be.true 113 | expect(socketStub.disconnect.calledOnce).to.be.true 114 | expect(socketStub.destroy.calledOnce).to.be.true 115 | 116 | done() 117 | } 118 | }) 119 | }) 120 | 121 | describe('chrome.sockets', function () { 122 | beforeEach(function () { 123 | // create chrome.socket stub 124 | var ChromeSocket = function () { } 125 | ChromeSocket.prototype.create = function () { } 126 | ChromeSocket.prototype.connect = function () { } 127 | ChromeSocket.prototype.disconnect = function () { } 128 | ChromeSocket.prototype.send = function () { } 129 | ChromeSocket.prototype.secure = function () { } 130 | ChromeSocket.prototype.setPaused = function () { } 131 | 132 | chrome.socket = undefined 133 | socketStub = sinon.createStubInstance(ChromeSocket) 134 | chrome.sockets = { 135 | tcp: socketStub 136 | } 137 | 138 | chrome.runtime = { 139 | getPlatformInfo: fn => { fn({ os: 'cordova' }) } 140 | } 141 | 142 | socketStub.onReceive = { 143 | addListener: function (fn) { 144 | setTimeout(() => { fn({ socketId: 42, data: testData.buffer }) }, 50) 145 | } 146 | } 147 | 148 | socketStub.onReceiveError = { 149 | addListener: function () { } 150 | } 151 | 152 | socketStub.create.yields({ 153 | socketId: 42 154 | }) 155 | socketStub.connect.withArgs(42, '127.0.0.1', 9000).yieldsAsync(0) 156 | socketStub.secure.withArgs(42).yieldsAsync(0) 157 | socketStub.setPaused.withArgs(42, true).yieldsAsync() 158 | socketStub.setPaused.withArgs(42, false).yieldsAsync() 159 | socketStub.send.withArgs(42).yieldsAsync({ 160 | bytesWritten: 3 161 | }) 162 | }) 163 | 164 | it('should open, read, write, close without ssl', function (done) { 165 | var sent = false 166 | 167 | socket = TCPSocket.open('127.0.0.1', 9000, { 168 | useSecureTransport: false 169 | }) 170 | 171 | socket.onopen = function () { 172 | expect(socket._socketId).to.equal(42) 173 | expect(socket.ssl).to.be.false 174 | } 175 | 176 | socket.ondata = function (e) { 177 | var buf = new Uint8Array(e.data) 178 | expect(buf).to.deep.equal(testData) 179 | 180 | if (!sent) { 181 | sent = !sent 182 | socket.send(new Uint8Array([0, 1, 2]).buffer) 183 | } 184 | } 185 | 186 | socket.ondrain = function () { 187 | socket.close() 188 | } 189 | 190 | socket.onclose = function () { 191 | expect(socket.readyState).to.equal('closed') 192 | expect(socket._socketId).to.equal(0) 193 | expect(socketStub.create.calledOnce).to.be.true 194 | expect(socketStub.connect.calledOnce).to.be.true 195 | expect(socketStub.secure.called).to.be.false 196 | expect(socketStub.send.calledOnce).to.be.true 197 | expect(socketStub.disconnect.calledOnce).to.be.true 198 | expect(socketStub.setPaused.calledTwice).to.be.true 199 | 200 | done() 201 | } 202 | }) 203 | 204 | it('should open, read, write, close with ssl', function (done) { 205 | var sent = false 206 | 207 | socket = TCPSocket.open('127.0.0.1', 9000, { 208 | useSecureTransport: true 209 | }) 210 | 211 | socket.onopen = function () { 212 | expect(socket._socketId).to.equal(42) 213 | expect(socket.ssl).to.be.true 214 | } 215 | 216 | socket.ondata = function (e) { 217 | var buf = new Uint8Array(e.data) 218 | expect(buf).to.deep.equal(testData) 219 | 220 | if (!sent) { 221 | sent = !sent 222 | socket.send(new Uint8Array([0, 1, 2]).buffer) 223 | } 224 | } 225 | 226 | socket.ondrain = function () { 227 | socket.close() 228 | } 229 | 230 | socket.onclose = function () { 231 | expect(socket.readyState).to.equal('closed') 232 | expect(socket._socketId).to.equal(0) 233 | expect(socketStub.create.calledOnce).to.be.true 234 | expect(socketStub.connect.calledOnce).to.be.true 235 | expect(socketStub.secure.calledOnce).to.be.true 236 | expect(socketStub.send.calledOnce).to.be.true 237 | expect(socketStub.disconnect.calledOnce).to.be.true 238 | expect(socketStub.setPaused.calledTwice).to.be.true 239 | 240 | done() 241 | } 242 | }) 243 | }) 244 | }) 245 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tcp-socket 2 | ========== 3 | 4 | ## DEPRECATION NOTICE 5 | 6 | This project is not actively being maintained. If you're sending emails on a node.js-esque platform, please use Andris Reinman's [nodemailer](https://github.com/nodemailer/nodemailer). It is actively supported, more widely used and maintained offers more possibilities for sending mails than this project. 7 | 8 | Background: This project was created because there was no option of using SMTP in a browser environment. This use case has been eliminated since Chrome Apps reached end of life and Firefox OS was scrapped. If you're on an electron-based platform, please use the capabilities that come with a full fledged node.js backend. 9 | 10 | If you still feel this project has merit and you would like to be a maintainer, please reach out to me. 11 | 12 | 13 | []()[](https://greenkeeper.io/) [](https://travis-ci.org/emailjs/emailjs-tcp-socket) [](https://standardjs.com) [](https://kangax.github.io/compat-table/es6/) 14 | 15 | This shim brings [Mozilla-flavored](https://developer.mozilla.org/en-US/docs/WebAPI/TCP_Socket) version of the [Raw Socket API](http://www.w3.org/TR/raw-sockets/) to node.js, Chromium apps, Windows 10 UWP apps, and websockets (via socket.io). 16 | 17 | NB: Chrome Apps are going away, hence the Chrome socket implementation can be regarded as obsolete. 18 | 19 | https://github.com/emailjs/emailjs-imap-client/issues/158 20 | https://blog.chromium.org/2016/08/from-chrome-apps-to-web.html 21 | https://github.com/MobileChromeApps/mobile-chrome-apps/issues/269 22 | 23 | # Usage 24 | 25 | ``` 26 | npm install --save emailjs-tcp-socket 27 | ``` 28 | 29 | ```javascript 30 | import TCPSocket from 'emailjs-tcp-socket' 31 | ``` 32 | 33 | See also the [Mozilla TCPSocket API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/TCPSocket). 34 | 35 | ## #open 36 | 37 | ```javascript 38 | var tcpSocket = TCPSocket.open('127.0.0.1', 8000); 39 | var tlsSocket = TCPSocket.open('127.0.0.1', 9000, { 40 | useSecureTransport: true, 41 | ca: 'PEM-formatted X.509 TLS Cert' 42 | }); 43 | ``` 44 | 45 | A call to `TCPSocket.open` expects host and port, followed by further socket options: 46 | 47 | * useSecureTransport: `true` for TLS encryption, `false` for plaintext sockets. Defaults to `false`. 48 | * ca: Enables certificate pinning for platforms without native TLS implementations. Expects a PEM-encoded X.509 TLS certificate as a string. 49 | 50 | ## #upgradeToSecure() 51 | 52 | Established a secure channel via TLS. The upgradeToSecure method allows turning a TCP non secured connection into a secured one. `upgradeToSecure()` will return immediately. If the TLS negotiation fails, the socket will throw an error and close. The socket buffers writes that occur in the meantime and writes the data out altogether when the TLS handshake is done. If this behavior is a problem in your protocol, please open an issue and/or submit a PR. 53 | 54 | **A note on native TLS**: Native TLS support is varying throughout the platforms. If you want to use TLS on a platform that does not natively provide it, we fall back to [forge](https://github.com/digitalbazaar/forge) for TLS, and you must provide a certificate for pinning! 55 | 56 | The following platforms support TLS natively: 57 | 58 | * node.js and related (e.g. Electron) 59 | * Desktop Chrome Apps on Chrome M38+ with TLS connection (not STARTTLS!) 60 | * Windows StreamSocket 61 | 62 | The following implementations use forge as a TLS shim: 63 | 64 | * WebSockets 65 | * Chrome Apps with STARTTLS and Mobile Chrome Apps built with [cca](https://github.com/MobileChromeApps/mobile-chrome-apps) (chrome.sockets.tcp.secure is broken) 66 | 67 | On a platform where we fall back to forge for TLS, you can either supply the socket with a certificate, or use a trust-on-first-use based approach, where the socket is accepted in the first try and you will receive a callback with the certificate. Use this certificate in subsequent interactions with this host. Host authenticity is evaluated based on their Common Name (or SubjectAltNames) and the certificate's public key fingerprint. 68 | 69 | ```javascript 70 | var tls = navigator.TCPSocket.open('127.0.0.1', 9000, { useSecureTransport: true }) 71 | tls.oncert = pemEncodedCertificate => {} // do something useful with the certificate, e.g. store it and reuse it on a trust-on-first-use basis 72 | ``` 73 | 74 | Here's how the TLS shim will behave when presented with a server certificate: 75 | 76 | * If the server does not present a certificate, it rejects the connection 77 | * If the server presents a certificate with wrong/missing CN and/or wrong/missing SANs, it rejects the connection 78 | * If no certificate was pinned, it calls .oncert() with the pem-encoded certificate and accepts the connection 79 | * If a certificate was pinned, but the server presents another certificate (according to the public key fingerprint), it calls .oncert() to inform you about changes, but rejects the connection 80 | * If a certificate was pinned and the server certificate's public key fingerprint matches the pinned certificate, the connection is accepted. .oncert will **not** be called in this case! 81 | 82 | Please note that we can not synchronously ask whether that certificate is ok or not, since the TLS shim runs in a Web Worker. 83 | 84 | ## #close() 85 | 86 | ```javascript 87 | socket.close() 88 | ``` 89 | 90 | Closes the connection, invokes `.onclose` when socket is closed. 91 | 92 | ## #send(data) 93 | 94 | ```javascript 95 | socket.send(data) 96 | ``` 97 | 98 | Send an ArrayBuffer across the network. Backpressure is handled in the actual underlying socket implementations. 99 | 100 | ## Events 101 | 102 | ```javascript 103 | socket.onopen = () => {} // A handler for the open event. After this event, the socket is ready to send and receive data. 104 | socket.ondrain = () => {} // A handler for the drain event. This event is triggered each time the buffer of data is flushed. 105 | socket.onerror = (error) => {} // A handler for the error event. 106 | socket.ondata = (arraybuffer) => {} // A handler for the data event. This event is triggered each time data has been received. 107 | socket.onclose = () => {} // A handler for the close event. 108 | ``` 109 | 110 | ## Web Sockets 111 | 112 | Run the websocket proxy (socket.io + express) to use TCPSocket straight from the browser. Please note that there is a good reason for TCP sockets to not be avaiable in the open web. Handle this with extreme care. The WebSocket shim adds a new configuration object `ws` to `TCPSocket.open` 113 | 114 | * **url** is the url for the WebSocket proxy server (defaults to '/') 115 | * **options** are [Socket.io options](http://socket.io/docs/client-api/#io(url:string,-opts:object):socket) 116 | 117 | ```javascript 118 | var socket = TCPSocket.open('127.0.0.1', 9000, { 119 | ... 120 | ws: { 121 | url: 'http://localhost:8889', 122 | options: { 123 | upgrade: false 124 | } 125 | } 126 | }) 127 | ``` 128 | 129 | # Unavailable API 130 | 131 | The following API is not available with this shim: 132 | 133 | * #listen 134 | * #resume 135 | * #suspend 136 | 137 | # License 138 | 139 | This library is licensed under the MIT license. 140 | 141 | Copyright (c) 2014 Whiteout Networks 142 | 143 | Permission is hereby granted, free of charge, to any person obtaining a copy 144 | of this software and associated documentation files (the "Software"), to deal 145 | in the Software without restriction, including without limitation the rights 146 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 147 | copies of the Software, and to permit persons to whom the Software is 148 | furnished to do so, subject to the following conditions: 149 | 150 | The above copyright notice and this permission notice shall be included in all 151 | copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 154 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 155 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 156 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 157 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 158 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 159 | SOFTWARE. 160 | -------------------------------------------------------------------------------- /src/chrome-socket.js: -------------------------------------------------------------------------------- 1 | import { propOr } from 'ramda' 2 | import scheduleInNextEventLoop from './timeout' 3 | import createTls from './tls-utils' 4 | import { 5 | EVENT_INBOUND, EVENT_OUTBOUND, 6 | createMessage 7 | } from './worker-utils' 8 | 9 | export default class TCPSocket { 10 | static open (host, port, options = {}) { 11 | return new TCPSocket({ host, port, options }) 12 | } 13 | 14 | constructor ({ host, port, options }) { 15 | this.host = host 16 | this.port = port 17 | this.ssl = false 18 | this.bufferedAmount = 0 19 | this.readyState = 'connecting' 20 | this.binaryType = propOr('arraybuffer', 'binaryType')(options) 21 | 22 | if (this.binaryType !== 'arraybuffer') { 23 | throw new Error('Only arraybuffers are supported!') 24 | } 25 | 26 | this._ca = options.ca 27 | this._useTLS = propOr(false, 'useSecureTransport')(options) 28 | this._useSTARTTLS = false 29 | this._socketId = 0 30 | this._useLegacySocket = false 31 | this._useForgeTls = false 32 | 33 | // handles writes during starttls handshake, chrome socket only 34 | this._startTlsBuffer = [] 35 | this._startTlsHandshakeInProgress = false 36 | 37 | chrome.runtime.getPlatformInfo(platformInfo => { 38 | if (platformInfo.os.indexOf('cordova') !== -1) { 39 | // chrome.sockets.tcp.secure is not functional on cordova 40 | // https://github.com/MobileChromeApps/mobile-chrome-apps/issues/269 41 | this._useLegacySocket = false 42 | this._useForgeTls = true 43 | } else { 44 | this._useLegacySocket = true 45 | this._useForgeTls = false 46 | } 47 | 48 | if (this._useLegacySocket) { 49 | this._createLegacySocket() 50 | } else { 51 | this._createSocket() 52 | } 53 | }) 54 | } 55 | 56 | /** 57 | * Creates a socket using the deprecated chrome.socket API 58 | */ 59 | _createLegacySocket () { 60 | chrome.socket.create('tcp', {}, createInfo => { 61 | this._socketId = createInfo.socketId 62 | 63 | chrome.socket.connect(this._socketId, this.host, this.port, result => { 64 | if (result !== 0) { 65 | this.readyState = 'closed' 66 | this._emit('error', chrome.runtime.lastError) 67 | return 68 | } 69 | 70 | this._onSocketConnected() 71 | }) 72 | }) 73 | } 74 | 75 | /** 76 | * Creates a socket using chrome.sockets.tcp 77 | */ 78 | _createSocket () { 79 | chrome.sockets.tcp.create({}, createInfo => { 80 | this._socketId = createInfo.socketId 81 | 82 | // register for data events on the socket before connecting 83 | chrome.sockets.tcp.onReceive.addListener(readInfo => { 84 | if (readInfo.socketId === this._socketId) { 85 | // process the data available on the socket 86 | this._onData(readInfo.data) 87 | } 88 | }) 89 | 90 | // register for data error on the socket before connecting 91 | chrome.sockets.tcp.onReceiveError.addListener(readInfo => { 92 | if (readInfo.socketId === this._socketId) { 93 | // socket closed remotely or broken 94 | this.close() 95 | } 96 | }) 97 | 98 | chrome.sockets.tcp.setPaused(this._socketId, true, () => { 99 | chrome.sockets.tcp.connect(this._socketId, this.host, this.port, result => { 100 | if (result < 0) { 101 | this.readyState = 'closed' 102 | this._emit('error', chrome.runtime.lastError) 103 | return 104 | } 105 | 106 | this._onSocketConnected() 107 | }) 108 | }) 109 | }) 110 | } 111 | 112 | /** 113 | * Invoked once a socket has been connected: 114 | * - Kicks off TLS handshake, if necessary 115 | * - Starts reading from legacy socket, if necessary 116 | */ 117 | _onSocketConnected () { 118 | const read = () => { 119 | if (this._useLegacySocket) { 120 | // the tls handshake is done let's start reading from the legacy socket 121 | this._readLegacySocket() 122 | this._emit('open') 123 | } else { 124 | chrome.sockets.tcp.setPaused(this._socketId, false, () => { 125 | this._emit('open') 126 | }) 127 | } 128 | } 129 | 130 | if (!this._useTLS) { 131 | return read() 132 | } 133 | 134 | // do an immediate TLS handshake if this._useTLS === true 135 | this._upgradeToSecure(() => { read() }) 136 | } 137 | 138 | /** 139 | * Handles the rough edges for differences between chrome.socket and chrome.sockets.tcp 140 | * for upgrading to a TLS connection with or without forge 141 | */ 142 | _upgradeToSecure (callback = () => {}) { 143 | // invoked after chrome.socket.secure or chrome.sockets.tcp.secure have been upgraded 144 | const onUpgraded = tlsResult => { 145 | if (tlsResult !== 0) { 146 | this._emit('error', new Error('TLS handshake failed. Reason: ' + chrome.runtime.lastError.message)) 147 | this.close() 148 | return 149 | } 150 | 151 | this.ssl = true 152 | 153 | // empty the buffer 154 | while (this._startTlsBuffer.length) { 155 | this.send(this._startTlsBuffer.shift()) 156 | } 157 | 158 | callback() 159 | } 160 | 161 | if (!this._useLegacySocket && this.readyState !== 'open') { 162 | // use chrome.sockets.tcp.secure for TLS, not for STARTTLS! 163 | // use forge only for STARTTLS 164 | this._useForgeTls = false 165 | chrome.sockets.tcp.secure(this._socketId, onUpgraded) 166 | } else if (this._useLegacySocket) { 167 | chrome.socket.secure(this._socketId, onUpgraded) 168 | } else if (this._useForgeTls) { 169 | // setup the forge tls client or webworker as tls fallback 170 | createTls(this) 171 | callback() 172 | } 173 | } 174 | 175 | upgradeToSecure () { 176 | if (this.ssl || this._useSTARTTLS) { 177 | return 178 | } 179 | 180 | this._useSTARTTLS = true 181 | this._upgradeToSecure(() => { 182 | if (this._useLegacySocket) { 183 | this._readLegacySocket() // tls handshake is done, restart reading 184 | } 185 | }) 186 | } 187 | 188 | /** 189 | * Reads from a legacy chrome.socket. 190 | */ 191 | _readLegacySocket () { 192 | if (this._socketId === 0) { 193 | // the socket is closed. omit read and stop further reads 194 | return 195 | } 196 | 197 | // don't read from chrome.socket if we have chrome.socket.secure a handshake in progress! 198 | if ((this._useSTARTTLS || this._useTLS) && !this.ssl) { 199 | return 200 | } 201 | 202 | chrome.socket.read(this._socketId, readInfo => { 203 | // socket closed remotely or broken 204 | if (readInfo.resultCode <= 0) { 205 | this._socketId = 0 206 | this.close() 207 | return 208 | } 209 | 210 | // process the data available on the socket 211 | this._onData(readInfo.data) 212 | 213 | // Queue the next read. 214 | // If a STARTTLS handshake might be upcoming, postpone this onto 215 | // the task queue so the IMAP client has a chance to call upgradeToSecure; 216 | // without this, we might eat the beginning of the handshake. 217 | // If we are already secure, just call it (for performance). 218 | if (this.ssl) { 219 | this._readLegacySocket() 220 | } else { 221 | scheduleInNextEventLoop(() => this._readLegacySocket()) 222 | } 223 | }) 224 | } 225 | 226 | /** 227 | * Invoked when data has been read from the socket. Handles cases when to feed 228 | * the data available on the socket to forge. 229 | * 230 | * @param {ArrayBuffer} buffer The binary data read from the socket 231 | */ 232 | _onData (buffer) { 233 | if ((this._useTLS || this._useSTARTTLS) && this._useForgeTls) { 234 | // feed the data to the tls client 235 | if (this._tlsWorker) { 236 | this._tlsWorker.postMessage(createMessage(EVENT_INBOUND, buffer), [buffer]) 237 | } else { 238 | this._tls.processInbound(buffer) 239 | } 240 | } else { 241 | // emit data event 242 | this._emit('data', buffer) 243 | } 244 | } 245 | 246 | /** 247 | * Closes the socket 248 | * @return {[type]} [description] 249 | */ 250 | close () { 251 | this.readyState = 'closing' 252 | 253 | if (this._socketId !== 0) { 254 | if (this._useLegacySocket) { 255 | // close legacy socket 256 | chrome.socket.disconnect(this._socketId) 257 | chrome.socket.destroy(this._socketId) 258 | } else { 259 | // close socket 260 | chrome.sockets.tcp.disconnect(this._socketId) 261 | } 262 | 263 | this._socketId = 0 264 | } 265 | 266 | // terminate the tls worker 267 | if (this._tlsWorker) { 268 | this._tlsWorker.terminate() 269 | this._tlsWorker = undefined 270 | } 271 | 272 | this._emit('close') 273 | } 274 | 275 | send (buffer) { 276 | if (!this._useForgeTls && this._useSTARTTLS && !this.ssl) { 277 | // buffer the unprepared data until chrome.socket(s.tcp) handshake is done 278 | this._startTlsBuffer.push(buffer) 279 | } else if (this._useForgeTls && (this._useTLS || this._useSTARTTLS)) { 280 | // give buffer to forge to be prepared for tls 281 | if (this._tlsWorker) { 282 | this._tlsWorker.postMessage(createMessage(EVENT_OUTBOUND, buffer), [buffer]) 283 | } else { 284 | this._tls.prepareOutbound(buffer) 285 | } 286 | } else { 287 | // send the arraybuffer 288 | this._send(buffer) 289 | } 290 | } 291 | 292 | _send (data) { 293 | if (this._socketId === 0) { 294 | // the socket is closed. 295 | return 296 | } 297 | 298 | if (this._useLegacySocket) { 299 | chrome.socket.write(this._socketId, data, writeInfo => { 300 | if (writeInfo.bytesWritten < 0 && this._socketId !== 0) { 301 | // if the socket is already 0, it has already been closed. no need to alert then... 302 | this._emit('error', new Error('Could not write ' + data.byteLength + ' bytes to socket ' + this._socketId + '. Chrome error code: ' + writeInfo.bytesWritten)) 303 | this._socketId = 0 304 | this.close() 305 | 306 | return 307 | } 308 | 309 | this._emit('drain') 310 | }) 311 | } else { 312 | chrome.sockets.tcp.send(this._socketId, data, sendInfo => { 313 | if (sendInfo.bytesSent < 0 && this._socketId !== 0) { 314 | // if the socket is already 0, it has already been closed. no need to alert then... 315 | this._emit('error', new Error('Could not write ' + data.byteLength + ' bytes to socket ' + this._socketId + '. Chrome error code: ' + sendInfo.bytesSent)) 316 | this.close() 317 | 318 | return 319 | } 320 | 321 | this._emit('drain') 322 | }) 323 | } 324 | } 325 | 326 | _emit (type, data) { 327 | const target = this 328 | switch (type) { 329 | case 'open': 330 | this.readyState = 'open' 331 | this.onopen && this.onopen({ target, type, data }) 332 | break 333 | case 'error': 334 | this.onerror && this.onerror({ target, type, data }) 335 | break 336 | case 'data': 337 | this.ondata && this.ondata({ target, type, data }) 338 | break 339 | case 'drain': 340 | this.ondrain && this.ondrain({ target, type, data }) 341 | break 342 | case 'close': 343 | this.readyState = 'closed' 344 | this.onclose && this.onclose({ target, type, data }) 345 | break 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /dist/node-socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _ramda = require('ramda'); 10 | 11 | var _net = require('net'); 12 | 13 | var _net2 = _interopRequireDefault(_net); 14 | 15 | var _tls = require('tls'); 16 | 17 | var _tls2 = _interopRequireDefault(_tls); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 22 | 23 | var TCPSocket = function () { 24 | _createClass(TCPSocket, null, [{ 25 | key: 'open', 26 | value: function open(host, port) { 27 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 28 | 29 | return new TCPSocket({ host: host, port: port, options: options }); 30 | } 31 | }]); 32 | 33 | function TCPSocket(_ref) { 34 | var _this = this; 35 | 36 | var host = _ref.host, 37 | port = _ref.port, 38 | options = _ref.options; 39 | 40 | _classCallCheck(this, TCPSocket); 41 | 42 | this.host = host; 43 | this.port = port; 44 | this.ssl = (0, _ramda.propOr)(false, 'useSecureTransport')(options); 45 | this.bufferedAmount = 0; 46 | this.readyState = 'connecting'; 47 | this.binaryType = (0, _ramda.propOr)('arraybuffer', 'binaryType')(options); 48 | 49 | if (this.binaryType !== 'arraybuffer') { 50 | throw new Error('Only arraybuffers are supported!'); 51 | } 52 | 53 | this._socket = this.ssl ? _tls2.default.connect(this.port, this.host, {}, function () { 54 | return _this._emit('open'); 55 | }) : _net2.default.connect(this.port, this.host, function () { 56 | return _this._emit('open'); 57 | }); 58 | 59 | // add all event listeners to the new socket 60 | this._attachListeners(); 61 | } 62 | 63 | _createClass(TCPSocket, [{ 64 | key: '_attachListeners', 65 | value: function _attachListeners() { 66 | var _this2 = this; 67 | 68 | this._socket.on('data', function (nodeBuf) { 69 | return _this2._emit('data', nodeBuffertoArrayBuffer(nodeBuf)); 70 | }); 71 | this._socket.on('error', function (error) { 72 | // Ignore ECONNRESET errors. For the app this is the same as normal close 73 | if (error.code !== 'ECONNRESET') { 74 | _this2._emit('error', error); 75 | } 76 | _this2.close(); 77 | }); 78 | 79 | this._socket.on('end', function () { 80 | return _this2._emit('close'); 81 | }); 82 | } 83 | }, { 84 | key: '_removeListeners', 85 | value: function _removeListeners() { 86 | this._socket.removeAllListeners('data'); 87 | this._socket.removeAllListeners('end'); 88 | this._socket.removeAllListeners('error'); 89 | } 90 | }, { 91 | key: '_emit', 92 | value: function _emit(type, data) { 93 | var target = this; 94 | switch (type) { 95 | case 'open': 96 | this.readyState = 'open'; 97 | this.onopen && this.onopen({ target: target, type: type, data: data }); 98 | break; 99 | case 'error': 100 | this.onerror && this.onerror({ target: target, type: type, data: data }); 101 | break; 102 | case 'data': 103 | this.ondata && this.ondata({ target: target, type: type, data: data }); 104 | break; 105 | case 'drain': 106 | this.ondrain && this.ondrain({ target: target, type: type, data: data }); 107 | break; 108 | case 'close': 109 | this.readyState = 'closed'; 110 | this.onclose && this.onclose({ target: target, type: type, data: data }); 111 | break; 112 | } 113 | } 114 | 115 | // 116 | // API 117 | // 118 | 119 | }, { 120 | key: 'close', 121 | value: function close() { 122 | this.readyState = 'closing'; 123 | this._socket.end(); 124 | } 125 | }, { 126 | key: 'send', 127 | value: function send(data) { 128 | // convert data to string or node buffer 129 | this._socket.write(arrayBufferToNodeBuffer(data), this._emit.bind(this, 'drain')); 130 | } 131 | }, { 132 | key: 'upgradeToSecure', 133 | value: function upgradeToSecure() { 134 | var _this3 = this; 135 | 136 | if (this.ssl) return; 137 | 138 | this._removeListeners(); 139 | this._socket = _tls2.default.connect({ socket: this._socket }, function () { 140 | _this3.ssl = true; 141 | }); 142 | this._attachListeners(); 143 | } 144 | }]); 145 | 146 | return TCPSocket; 147 | }(); 148 | 149 | exports.default = TCPSocket; 150 | 151 | 152 | var nodeBuffertoArrayBuffer = function nodeBuffertoArrayBuffer(buf) { 153 | return Uint8Array.from(buf).buffer; 154 | }; 155 | var arrayBufferToNodeBuffer = function arrayBufferToNodeBuffer(ab) { 156 | return Buffer.from(new Uint8Array(ab)); 157 | }; 158 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9ub2RlLXNvY2tldC5qcyJdLCJuYW1lcyI6WyJUQ1BTb2NrZXQiLCJob3N0IiwicG9ydCIsIm9wdGlvbnMiLCJzc2wiLCJidWZmZXJlZEFtb3VudCIsInJlYWR5U3RhdGUiLCJiaW5hcnlUeXBlIiwiRXJyb3IiLCJfc29ja2V0IiwiY29ubmVjdCIsIl9lbWl0IiwiX2F0dGFjaExpc3RlbmVycyIsIm9uIiwibm9kZUJ1ZmZlcnRvQXJyYXlCdWZmZXIiLCJub2RlQnVmIiwiZXJyb3IiLCJjb2RlIiwiY2xvc2UiLCJyZW1vdmVBbGxMaXN0ZW5lcnMiLCJ0eXBlIiwiZGF0YSIsInRhcmdldCIsIm9ub3BlbiIsIm9uZXJyb3IiLCJvbmRhdGEiLCJvbmRyYWluIiwib25jbG9zZSIsImVuZCIsIndyaXRlIiwiYXJyYXlCdWZmZXJUb05vZGVCdWZmZXIiLCJiaW5kIiwiX3JlbW92ZUxpc3RlbmVycyIsInNvY2tldCIsIlVpbnQ4QXJyYXkiLCJmcm9tIiwiYnVmIiwiYnVmZmVyIiwiYWIiLCJCdWZmZXIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBQUE7O0FBQ0E7Ozs7QUFDQTs7Ozs7Ozs7SUFFcUJBLFM7Ozt5QkFDTkMsSSxFQUFNQyxJLEVBQW9CO0FBQUEsVUFBZEMsT0FBYyx1RUFBSixFQUFJOztBQUNyQyxhQUFPLElBQUlILFNBQUosQ0FBYyxFQUFFQyxVQUFGLEVBQVFDLFVBQVIsRUFBY0MsZ0JBQWQsRUFBZCxDQUFQO0FBQ0Q7OztBQUVELDJCQUFzQztBQUFBOztBQUFBLFFBQXZCRixJQUF1QixRQUF2QkEsSUFBdUI7QUFBQSxRQUFqQkMsSUFBaUIsUUFBakJBLElBQWlCO0FBQUEsUUFBWEMsT0FBVyxRQUFYQSxPQUFXOztBQUFBOztBQUNwQyxTQUFLRixJQUFMLEdBQVlBLElBQVo7QUFDQSxTQUFLQyxJQUFMLEdBQVlBLElBQVo7QUFDQSxTQUFLRSxHQUFMLEdBQVcsbUJBQU8sS0FBUCxFQUFjLG9CQUFkLEVBQW9DRCxPQUFwQyxDQUFYO0FBQ0Es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YW9CLEtBQWIsQ0FBbUJDLHdCQUF3QlQsSUFBeEIsQ0FBbkIsRUFBa0QsS0FBS1YsS0FBTCxDQUFXb0IsSUFBWCxDQUFnQixJQUFoQixFQUFzQixPQUF0QixDQUFsRDtBQUNEOzs7c0NBRWtCO0FBQUE7O0FBQ2pCLFVBQUksS0FBSzNCLEdBQVQsRUFBYzs7QUFFZCxXQUFLNEIsZ0JBQUw7QUFDQSxXQUFLdkIsT0FBTCxHQUFlLGNBQUlDLE9BQUosQ0FBWSxFQUFFdUIsUUFBUSxLQUFLeEIsT0FBZixFQUFaLEVBQXNDLFlBQU07QUFBRSxlQUFLTCxHQUFMLEdBQVcsSUFBWDtBQUFpQixPQUEvRCxDQUFmO0FBQ0EsV0FBS1EsZ0JBQUw7QUFDRDs7Ozs7O2tCQXZGa0JaLFM7OztBQTBGckIsSUFBTWMsMEJBQTBCLFNBQTFCQSx1QkFBMEI7QUFBQSxTQUFPb0IsV0FBV0MsSUFBWCxDQUFnQkMsR0FBaEIsRUFBcUJDLE1BQTVCO0FBQUEsQ0FBaEM7QUFDQSxJQUFNUCwwQkFBMEIsU0FBMUJBLHVCQUEwQixDQUFDUSxFQUFEO0FBQUEsU0FBUUMsT0FBT0osSUFBUCxDQUFZLElBQUlELFVBQUosQ0FBZUksRUFBZixDQUFaLENBQVI7QUFBQSxDQUFoQyIsImZpbGUiOiJub2RlLXNvY2tldC5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHByb3BPciB9IGZyb20gJ3JhbWRhJ1xuaW1wb3J0IG5ldCBmcm9tICduZXQnXG5pbXBvcnQgdGxzIGZyb20gJ3RscydcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgVENQU29ja2V0IHtcbiAgc3RhdGljIG9wZW4gKGhvc3QsIHBvcnQsIG9wdGlvbnMgPSB7fSkge1xuICAgIHJldHVybiBuZXcgVENQU29ja2V0KHsgaG9zdCwgcG9ydCwgb3B0aW9ucyB9KVxuICB9XG5cbiAgY29uc3RydWN0b3IgKHsgaG9zdCwgcG9ydCwgb3B0aW9ucyB9KSB7XG4gICAgdGhpcy5ob3N0ID0gaG9zdFxuICAgIHRoaXMucG9ydCA9IHBvcnRcbiAgICB0aGlzLnNzbCA9IHByb3BPcihmYWxzZSwgJ3VzZVNlY3VyZVRyYW5zcG9ydCcpKG9wdGlvbnMpXG4gICAgdGhpcy5idWZmZXJlZEFtb3VudCA9IDBcbiAgICB0aGlzLnJlYWR5U3RhdGUgPSAnY29ubmVjdGluZydcbiAgICB0aGlzLmJpbmFyeVR5cGUgPSBwcm9wT3IoJ2FycmF5YnVmZmVyJywgJ2JpbmFyeVR5cGUnKShvcHRpb25zKVxuXG4gICAgaWYgKHRoaXMuYmluYXJ5VHlwZSAhPT0gJ2FycmF5YnVmZmVyJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdPbmx5IGFycmF5YnVmZmVycyBhcmUgc3VwcG9ydGVkIScpXG4gICAgfVxuXG4gICAgdGhpcy5fc29ja2V0ID0gdGhpcy5zc2xcbiAgICAgID8gdGxzLmNvbm5lY3QodGhpcy5wb3J0LCB0aGlzLmhvc3QsIHsgfSwgKCkgPT4gdGhpcy5fZW1pdCgnb3BlbicpKVxuICAgICAgOiBuZXQuY29ubmVjdCh0aGlzLnBvcnQsIHRoaXMuaG9zdCwgKCkgPT4gdGhpcy5fZW1pdCgnb3BlbicpKVxuXG4gICAgLy8gYWRkIGFsbCBldmVudCBsaXN0ZW5lcnMgdG8gdGhlIG5ldyBzb2NrZXRcbiAgICB0aGlzLl9hdHRhY2hMaXN0ZW5lcnMoKVxuICB9XG5cbiAgX2F0dGFjaExpc3RlbmVycyAoKSB7XG4gICAgdGhpcy5fc29ja2V0Lm9uKCdkYXRhJywgbm9kZUJ1ZiA9PiB0aGlzLl9lbWl0KCdkYXRhJywgbm9kZUJ1ZmZlcnRvQXJyYXlCdWZmZXIobm9kZUJ1ZikpKVxuICAgIHRoaXMuX3NvY2tldC5vbignZXJyb3InLCBlcnJvciA9PiB7XG4gICAgICAvLyBJZ25vcmUgRUNPTk5SRVNFVCBlcnJvcnMuIEZvciB0aGUgYXBwIHRoaXMgaXMgdGhlIHNhbWUgYXMgbm9ybWFsIGNsb3NlXG4gICAgICBpZiAoZXJyb3IuY29kZSAhPT0gJ0VDT05OUkVTRVQnKSB7XG4gICAgICAgIHRoaXMuX2VtaXQoJ2Vycm9yJywgZXJyb3IpXG4gICAgICB9XG4gICAgICB0aGlzLmNsb3NlKClcbiAgICB9KVxuXG4gICAgdGhpcy5fc29ja2V0Lm9uKCdlbmQnLCAoKSA9PiB0aGlzLl9lbWl0KCdjbG9zZScpKVxuICB9XG5cbiAgX3JlbW92ZUxpc3RlbmVycyAoKSB7XG4gICAgdGhpcy5fc29ja2V0LnJlbW92ZUFsbExpc3RlbmVycygnZGF0YScpXG4gICAgdGhpcy5fc29ja2V0LnJlbW92ZUFsbExpc3RlbmVycygnZW5kJylcbiAgICB0aGlzLl9zb2NrZXQucmVtb3ZlQWxsTGlzdGVuZXJzKCdlcnJvcicpXG4gIH1cblxuICBfZW1pdCAodHlwZSwgZGF0YSkge1xuICAgIGNvbnN0IHRhcmdldCA9IHRoaXNcbiAgICBzd2l0Y2ggKHR5cGUpIHtcbiAgICAgIGNhc2UgJ29wZW4nOlxuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSAnb3BlbidcbiAgICAgICAgdGhpcy5vbm9wZW4gJiYgdGhpcy5vbm9wZW4oeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2Vycm9yJzpcbiAgICAgICAgdGhpcy5vbmVycm9yICYmIHRoaXMub25lcnJvcih7IHRhcmdldCwgdHlwZSwgZGF0YSB9KVxuICAgICAgICBicmVha1xuICAgICAgY2FzZSAnZGF0YSc6XG4gICAgICAgIHRoaXMub25kYXRhICYmIHRoaXMub25kYXRhKHsgdGFyZ2V0LCB0eXBlLCBkYXRhIH0pXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdkcmFpbic6XG4gICAgICAgIHRoaXMub25kcmFpbiAmJiB0aGlzLm9uZHJhaW4oeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2Nsb3NlJzpcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ2Nsb3NlZCdcbiAgICAgICAgdGhpcy5vbmNsb3NlICYmIHRoaXMub25jbG9zZSh7IHRhcmdldCwgdHlwZSwgZGF0YSB9KVxuICAgICAgICBicmVha1xuICAgIH1cbiAgfVxuXG4gIC8vXG4gIC8vIEFQSVxuICAvL1xuXG4gIGNsb3NlICgpIHtcbiAgICB0aGlzLnJlYWR5U3RhdGUgPSAnY2xvc2luZydcbiAgICB0aGlzLl9zb2NrZXQuZW5kKClcbiAgfVxuXG4gIHNlbmQgKGRhdGEpIHtcbiAgICAvLyBjb252ZXJ0IGRhdGEgdG8gc3RyaW5nIG9yIG5vZGUgYnVmZmVyXG4gICAgdGhpcy5fc29ja2V0LndyaXRlKGFycmF5QnVmZmVyVG9Ob2RlQnVmZmVyKGRhdGEpLCB0aGlzLl9lbWl0LmJpbmQodGhpcywgJ2RyYWluJykpXG4gIH1cblxuICB1cGdyYWRlVG9TZWN1cmUgKCkge1xuICAgIGlmICh0aGlzLnNzbCkgcmV0dXJuXG5cbiAgICB0aGlzLl9yZW1vdmVMaXN0ZW5lcnMoKVxuICAgIHRoaXMuX3NvY2tldCA9IHRscy5jb25uZWN0KHsgc29ja2V0OiB0aGlzLl9zb2NrZXQgfSwgKCkgPT4geyB0aGlzLnNzbCA9IHRydWUgfSlcbiAgICB0aGlzLl9hdHRhY2hMaXN0ZW5lcnMoKVxuICB9XG59XG5cbmNvbnN0IG5vZGVCdWZmZXJ0b0FycmF5QnVmZmVyID0gYnVmID0+IFVpbnQ4QXJyYXkuZnJvbShidWYpLmJ1ZmZlclxuY29uc3QgYXJyYXlCdWZmZXJUb05vZGVCdWZmZXIgPSAoYWIpID0+IEJ1ZmZlci5mcm9tKG5ldyBVaW50OEFycmF5KGFiKSlcbiJdfQ== -------------------------------------------------------------------------------- /dist/socketio-socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _ramda = require('ramda'); 10 | 11 | var _tlsUtils = require('./tls-utils'); 12 | 13 | var _tlsUtils2 = _interopRequireDefault(_tlsUtils); 14 | 15 | var _workerUtils = require('./worker-utils'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var TCPSocket = function () { 22 | _createClass(TCPSocket, null, [{ 23 | key: 'open', 24 | value: function open(host, port) { 25 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 26 | 27 | return new TCPSocket({ host: host, port: port, options: options }); 28 | } 29 | }]); 30 | 31 | function TCPSocket(_ref) { 32 | var _this = this; 33 | 34 | var host = _ref.host, 35 | port = _ref.port, 36 | options = _ref.options; 37 | 38 | _classCallCheck(this, TCPSocket); 39 | 40 | this.host = host; 41 | this.port = port; 42 | this.ssl = false; 43 | this.bufferedAmount = 0; 44 | this.readyState = 'connecting'; 45 | this.binaryType = (0, _ramda.propOr)('arraybuffer', 'binaryType')(options); 46 | 47 | if (this.binaryType !== 'arraybuffer') { 48 | throw new Error('Only arraybuffers are supported!'); 49 | } 50 | 51 | this._ca = options.ca; 52 | this._useTLS = (0, _ramda.propOr)(false, 'useSecureTransport')(options); 53 | this._useSTARTTLS = false; 54 | 55 | this._wsHost = (0, _ramda.pathOr)(window.location.origin, ['ws', 'url'])(options); 56 | this._wsOptions = (0, _ramda.pathOr)({}, ['ws', 'options'])(options); 57 | this._wsOptions.reconnection = this._wsOptions.reconnection || false; 58 | this._wsOptions.multiplex = this._wsOptions.multiplex || false; 59 | 60 | this._socket = io(this._wsHost, this._wsOptions); 61 | this._socket.emit('open', { host: host, port: port }, function (proxyHostname) { 62 | _this._proxyHostname = proxyHostname; 63 | if (_this._useTLS) { 64 | // the socket is up, do the tls handshake 65 | (0, _tlsUtils2.default)(_this); 66 | } else { 67 | // socket is up and running 68 | _this._emit('open', { 69 | proxyHostname: _this._proxyHostname 70 | }); 71 | } 72 | 73 | _this._socket.on('data', function (buffer) { 74 | if (_this._useTLS || _this._useSTARTTLS) { 75 | // feed the data to the tls socket 76 | if (_this._tlsWorker) { 77 | _this._tlsWorker.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_INBOUND, buffer), [buffer]); 78 | } else { 79 | _this._tls.processInbound(buffer); 80 | } 81 | } else { 82 | _this._emit('data', buffer); 83 | } 84 | }); 85 | 86 | _this._socket.on('error', function (message) { 87 | _this._emit('error', new Error(message)); 88 | _this.close(); 89 | }); 90 | 91 | _this._socket.on('close', function () { 92 | return _this.close(); 93 | }); 94 | }); 95 | } 96 | 97 | _createClass(TCPSocket, [{ 98 | key: 'close', 99 | value: function close() { 100 | this.readyState = 'closing'; 101 | 102 | this._socket.emit('end'); 103 | this._socket.disconnect(); 104 | 105 | if (this._tlsWorker) { 106 | this._tlsWorker.terminate(); 107 | } 108 | 109 | this._emit('close'); 110 | } 111 | }, { 112 | key: 'send', 113 | value: function send(buffer) { 114 | if (this._useTLS || this._useSTARTTLS) { 115 | // give buffer to forge to be prepared for tls 116 | if (this._tlsWorker) { 117 | this._tlsWorker.postMessage((0, _workerUtils.createMessage)(_workerUtils.EVENT_OUTBOUND, buffer), [buffer]); 118 | } else { 119 | this._tls.prepareOutbound(buffer); 120 | } 121 | return; 122 | } 123 | 124 | this._send(buffer); 125 | } 126 | }, { 127 | key: '_send', 128 | value: function _send(data) { 129 | var _this2 = this; 130 | 131 | this._socket.emit('data', data, function () { 132 | return _this2._emit('drain'); 133 | }); 134 | } 135 | }, { 136 | key: 'upgradeToSecure', 137 | value: function upgradeToSecure() { 138 | if (this.ssl || this._useSTARTTLS) return; 139 | 140 | this._useSTARTTLS = true; 141 | (0, _tlsUtils2.default)(this); 142 | } 143 | }, { 144 | key: '_emit', 145 | value: function _emit(type, data) { 146 | var target = this; 147 | switch (type) { 148 | case 'open': 149 | this.readyState = 'open'; 150 | this.onopen && this.onopen({ target: target, type: type, data: data }); 151 | break; 152 | case 'error': 153 | this.onerror && this.onerror({ target: target, type: type, data: data }); 154 | break; 155 | case 'data': 156 | this.ondata && this.ondata({ target: target, type: type, data: data }); 157 | break; 158 | case 'drain': 159 | this.ondrain && this.ondrain({ target: target, type: type, data: data }); 160 | break; 161 | case 'close': 162 | this.readyState = 'closed'; 163 | this.onclose && this.onclose({ target: target, type: type, data: data }); 164 | break; 165 | } 166 | } 167 | }]); 168 | 169 | return TCPSocket; 170 | }(); 171 | 172 | exports.default = TCPSocket; 173 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9zb2NrZXRpby1zb2NrZXQuanMiXSwibmFtZXMiOlsiVENQU29ja2V0IiwiaG9zdCIsInBvcnQiLCJvcHRpb25zIiwic3NsIiwiYnVmZmVyZWRBbW91bnQiLCJyZWFkeVN0YXRlIiwiYmluYXJ5VHlwZSIsIkVycm9yIiwiX2NhIiwiY2EiLCJfdXNlVExTIiwiX3VzZVNUQVJUVExTIiwiX3dzSG9zdCIsIndpbmRvdyIsImxvY2F0aW9uIiwib3JpZ2luIiwiX3dzT3B0aW9ucyIsInJlY29ubmVjdGlvbiIsIm11bHRpcGxleCIsIl9zb2NrZXQiLCJpbyIsImVtaXQiLCJfcHJveHlIb3N0bmFtZSIsInByb3h5SG9zdG5hbWUiLCJfZW1pdCIsIm9uIiwiX3Rsc1dvcmtlciIsInBvc3RNZXNzYWdlIiwiYnVmZmVyIiwiX3RscyIsInByb2Nlc3NJbmJvdW5kIiwibWVzc2FnZSIsImNsb3NlIiwiZGlzY29ubmVjdCIsInRlcm1pbmF0ZSIsInByZXBhcmVPdXRib3VuZCIsIl9zZW5kIiwiZGF0YSIsInR5cGUiLCJ0YXJnZXQiLCJvbm9wZW4iLCJvbmVycm9yIiwib25kYXRhIiwib25kcmFpbiIsIm9uY2xvc2UiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBQUE7O0FBQ0E7Ozs7QUFDQTs7Ozs7O0lBS3FCQSxTOzs7eUJBQ05DLEksRUFBTUMsSSxFQUFvQjtBQUFBLFVBQWRDLE9BQWMsdUVBQUosRUFBSTs7QUFDckMsYUFBTyxJQUFJSCxTQUFKLENBQWMsRUFBRUMsVUFBRixFQUFRQyxVQUFSLEVBQWNDLGdCQUFkLEVBQWQsQ0FBUDtBQUNEOzs7QUFFRCwyQkFBc0M7QUFBQTs7QUFBQSxRQUF2QkYsSUFBdUIsUUFBdkJBLElBQXVCO0FBQUEsUUFBakJDLElBQWlCLFFBQWpCQSxJQUFpQjtBQUFBLFFBQVhDLE9BQVcsUUFBWEEsT0FBVzs7QUFBQTs7QUFDcEMsU0FBS0YsSUFBTCxHQUFZQSxJQUFaO0FBQ0EsU0FBS0MsSUFBTCxHQUFZQSxJQUFaO0FBQ0EsU0FB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QUtNLE9BQUwsSUFBZ0IsS0FBS0EsT0FBTCxDQUFhLEVBQUVKLGNBQUYsRUFBVUQsVUFBVixFQUFnQkQsVUFBaEIsRUFBYixDQUFoQjtBQUNBO0FBQ0YsYUFBSyxPQUFMO0FBQ0UsZUFBS2hDLFVBQUwsR0FBa0IsUUFBbEI7QUFDQSxlQUFLdUMsT0FBTCxJQUFnQixLQUFLQSxPQUFMLENBQWEsRUFBRUwsY0FBRixFQUFVRCxVQUFWLEVBQWdCRCxVQUFoQixFQUFiLENBQWhCO0FBQ0E7QUFqQko7QUFtQkQ7Ozs7OztrQkF4SGtCdEMsUyIsImZpbGUiOiJzb2NrZXRpby1zb2NrZXQuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBwYXRoT3IsIHByb3BPciB9IGZyb20gJ3JhbWRhJ1xuaW1wb3J0IGNyZWF0ZVRscyBmcm9tICcuL3Rscy11dGlscydcbmltcG9ydCB7XG4gIEVWRU5UX0lOQk9VTkQsIEVWRU5UX09VVEJPVU5ELFxuICBjcmVhdGVNZXNzYWdlXG59IGZyb20gJy4vd29ya2VyLXV0aWxzJ1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBUQ1BTb2NrZXQge1xuICBzdGF0aWMgb3BlbiAoaG9zdCwgcG9ydCwgb3B0aW9ucyA9IHt9KSB7XG4gICAgcmV0dXJuIG5ldyBUQ1BTb2NrZXQoeyBob3N0LCBwb3J0LCBvcHRpb25zIH0pXG4gIH1cblxuICBjb25zdHJ1Y3RvciAoeyBob3N0LCBwb3J0LCBvcHRpb25zIH0pIHtcbiAgICB0aGlzLmhvc3QgPSBob3N0XG4gICAgdGhpcy5wb3J0ID0gcG9ydFxuICAgIHRoaXMuc3NsID0gZmFsc2VcbiAgICB0aGlzLmJ1ZmZlcmVkQW1vdW50ID0gMFxuICAgIHRoaXMucmVhZHlTdGF0ZSA9ICdjb25uZWN0aW5nJ1xuICAgIHRoaXMuYmluYXJ5VHlwZSA9IHByb3BPcignYXJyYXlidWZmZXInLCAnYmluYXJ5VHlwZScpKG9wdGlvbnMpXG5cbiAgICBpZiAodGhpcy5iaW5hcnlUeXBlICE9PSAnYXJyYXlidWZmZXInKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ09ubHkgYXJyYXlidWZmZXJzIGFyZSBzdXBwb3J0ZWQhJylcbiAgICB9XG5cbiAgICB0aGlzLl9jYSA9IG9wdGlvbnMuY2FcbiAgICB0aGlzLl91c2VUTFMgPSBwcm9wT3IoZmFsc2UsICd1c2VTZWN1cmVUcmFuc3BvcnQnKShvcHRpb25zKVxuICAgIHRoaXMuX3VzZVNUQVJUVExTID0gZmFsc2VcblxuICAgIHRoaXMuX3dzSG9zdCA9IHBhdGhPcih3aW5kb3cubG9jYXRpb24ub3JpZ2luLCBbJ3dzJywgJ3VybCddKShvcHRpb25zKVxuICAgIHRoaXMuX3dzT3B0aW9ucyA9IHBhdGhPcih7fSwgWyd3cycsICdvcHRpb25zJ10pKG9wdGlvbnMpXG4gICAgdGhpcy5fd3NPcHRpb25zLnJlY29ubmVjdGlvbiA9IHRoaXMuX3dzT3B0aW9ucy5yZWNvbm5lY3Rpb24gfHwgZmFsc2VcbiAgICB0aGlzLl93c09wdGlvbnMubXVsdGlwbGV4ID0gdGhpcy5fd3NPcHRpb25zLm11bHRpcGxleCB8fCBmYWxzZVxuXG4gICAgdGhpcy5fc29ja2V0ID0gaW8odGhpcy5fd3NIb3N0LCB0aGlzLl93c09wdGlvbnMpXG4gICAgdGhpcy5fc29ja2V0LmVtaXQoJ29wZW4nLCB7IGhvc3QsIHBvcnQgfSwgcHJveHlIb3N0bmFtZSA9PiB7XG4gICAgICB0aGlzLl9wcm94eUhvc3RuYW1lID0gcHJveHlIb3N0bmFtZVxuICAgICAgaWYgKHRoaXMuX3VzZVRMUykge1xuICAgICAgICAvLyB0aGUgc29ja2V0IGlzIHVwLCBkbyB0aGUgdGxzIGhhbmRzaGFrZVxuICAgICAgICBjcmVhdGVUbHModGhpcylcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIHNvY2tldCBpcyB1cCBhbmQgcnVubmluZ1xuICAgICAgICB0aGlzLl9lbWl0KCdvcGVuJywge1xuICAgICAgICAgIHByb3h5SG9zdG5hbWU6IHRoaXMuX3Byb3h5SG9zdG5hbWVcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgdGhpcy5fc29ja2V0Lm9uKCdkYXRhJywgYnVmZmVyID0+IHtcbiAgICAgICAgaWYgKHRoaXMuX3VzZVRMUyB8fCB0aGlzLl91c2VTVEFSVFRMUykge1xuICAgICAgICAgIC8vIGZlZWQgdGhlIGRhdGEgdG8gdGhlIHRscyBzb2NrZXRcbiAgICAgICAgICBpZiAodGhpcy5fdGxzV29ya2VyKSB7XG4gICAgICAgICAgICB0aGlzLl90bHNXb3JrZXIucG9zdE1lc3NhZ2UoY3JlYXRlTWVzc2FnZShFVkVOVF9JTkJPVU5ELCBidWZmZXIpLCBbYnVmZmVyXSlcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fdGxzLnByb2Nlc3NJbmJvdW5kKGJ1ZmZlcilcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhpcy5fZW1pdCgnZGF0YScsIGJ1ZmZlcilcbiAgICAgICAgfVxuICAgICAgfSlcblxuICAgICAgdGhpcy5fc29ja2V0Lm9uKCdlcnJvcicsIG1lc3NhZ2UgPT4ge1xuICAgICAgICB0aGlzLl9lbWl0KCdlcnJvcicsIG5ldyBFcnJvcihtZXNzYWdlKSlcbiAgICAgICAgdGhpcy5jbG9zZSgpXG4gICAgICB9KVxuXG4gICAgICB0aGlzLl9zb2NrZXQub24oJ2Nsb3NlJywgKCkgPT4gdGhpcy5jbG9zZSgpKVxuICAgIH0pXG4gIH1cblxuICBjbG9zZSAoKSB7XG4gICAgdGhpcy5yZWFkeVN0YXRlID0gJ2Nsb3NpbmcnXG5cbiAgICB0aGlzLl9zb2NrZXQuZW1pdCgnZW5kJylcbiAgICB0aGlzLl9zb2NrZXQuZGlzY29ubmVjdCgpXG5cbiAgICBpZiAodGhpcy5fdGxzV29ya2VyKSB7XG4gICAgICB0aGlzLl90bHNXb3JrZXIudGVybWluYXRlKClcbiAgICB9XG5cbiAgICB0aGlzLl9lbWl0KCdjbG9zZScpXG4gIH1cblxuICBzZW5kIChidWZmZXIpIHtcbiAgICBpZiAodGhpcy5fdXNlVExTIHx8IHRoaXMuX3VzZVNUQVJUVExTKSB7XG4gICAgICAvLyBnaXZlIGJ1ZmZlciB0byBmb3JnZSB0byBiZSBwcmVwYXJlZCBmb3IgdGxzXG4gICAgICBpZiAodGhpcy5fdGxzV29ya2VyKSB7XG4gICAgICAgIHRoaXMuX3Rsc1dvcmtlci5wb3N0TWVzc2FnZShjcmVhdGVNZXNzYWdlKEVWRU5UX09VVEJPVU5ELCBidWZmZXIpLCBbYnVmZmVyXSlcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuX3Rscy5wcmVwYXJlT3V0Ym91bmQoYnVmZmVyKVxuICAgICAgfVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgdGhpcy5fc2VuZChidWZmZXIpXG4gIH1cblxuICBfc2VuZCAoZGF0YSkge1xuICAgIHRoaXMuX3NvY2tldC5lbWl0KCdkYXRhJywgZGF0YSwgKCkgPT4gdGhpcy5fZW1pdCgnZHJhaW4nKSlcbiAgfVxuXG4gIHVwZ3JhZGVUb1NlY3VyZSAoKSB7XG4gICAgaWYgKHRoaXMuc3NsIHx8IHRoaXMuX3VzZVNUQVJUVExTKSByZXR1cm5cblxuICAgIHRoaXMuX3VzZVNUQVJUVExTID0gdHJ1ZVxuICAgIGNyZWF0ZVRscyh0aGlzKVxuICB9XG5cbiAgX2VtaXQgKHR5cGUsIGRhdGEpIHtcbiAgICBjb25zdCB0YXJnZXQgPSB0aGlzXG4gICAgc3dpdGNoICh0eXBlKSB7XG4gICAgICBjYXNlICdvcGVuJzpcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ29wZW4nXG4gICAgICAgIHRoaXMub25vcGVuICYmIHRoaXMub25vcGVuKHsgdGFyZ2V0LCB0eXBlLCBkYXRhIH0pXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgIHRoaXMub25lcnJvciAmJiB0aGlzLm9uZXJyb3IoeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2RhdGEnOlxuICAgICAgICB0aGlzLm9uZGF0YSAmJiB0aGlzLm9uZGF0YSh7IHRhcmdldCwgdHlwZSwgZGF0YSB9KVxuICAgICAgICBicmVha1xuICAgICAgY2FzZSAnZHJhaW4nOlxuICAgICAgICB0aGlzLm9uZHJhaW4gJiYgdGhpcy5vbmRyYWluKHsgdGFyZ2V0LCB0eXBlLCBkYXRhIH0pXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdjbG9zZSc6XG4gICAgICAgIHRoaXMucmVhZHlTdGF0ZSA9ICdjbG9zZWQnXG4gICAgICAgIHRoaXMub25jbG9zZSAmJiB0aGlzLm9uY2xvc2UoeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICB9XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /dist/tls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _nodeForge = require('node-forge'); 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 12 | 13 | var TlsClient = function () { 14 | function TlsClient() { 15 | var _this = this; 16 | 17 | _classCallCheck(this, TlsClient); 18 | 19 | this.open = false; 20 | this._outboundBuffer = []; 21 | 22 | this._tls = _nodeForge.tls.createConnection({ 23 | server: false, 24 | verify: function verify(connection, verified, depth, certs) { 25 | if (!(certs && certs[0])) { 26 | return false; 27 | } 28 | 29 | if (!_this.verifyCertificate(certs[0], _this._host)) { 30 | return false; 31 | } 32 | 33 | /* 34 | * Please see the readme for an explanation of the behavior without a native TLS stack! 35 | */ 36 | 37 | // without a pinned certificate, we'll just accept the connection and notify the upper layer 38 | if (!_this._ca) { 39 | // notify the upper layer of the new cert 40 | _this.tlscert(_nodeForge.pki.certificateToPem(certs[0])); 41 | // succeed only if this.tlscert is implemented (otherwise forge catches the error) 42 | return true; 43 | } 44 | 45 | // if we have a pinned certificate, things get a little more complicated: 46 | // - leaf certificates pin the host directly, e.g. for self-signed certificates 47 | // - we also allow intermediate certificates, for providers that are able to sign their own certs. 48 | 49 | // detect if this is a certificate used for signing by testing if the common name different from the hostname. 50 | // also, an intermediate cert has no SANs, at least none that match the hostname. 51 | if (!_this.verifyCertificate(_this._ca, _this._host)) { 52 | // verify certificate through a valid certificate chain 53 | return _this._ca.verify(certs[0]); 54 | } 55 | 56 | // verify certificate through host certificate pinning 57 | var fpPinned = _nodeForge.pki.getPublicKeyFingerprint(_this._ca.publicKey, { 58 | encoding: 'hex' 59 | }); 60 | var fpRemote = _nodeForge.pki.getPublicKeyFingerprint(certs[0].publicKey, { 61 | encoding: 'hex' 62 | }); 63 | 64 | // check if cert fingerprints match 65 | if (fpPinned === fpRemote) { 66 | return true; 67 | } 68 | 69 | // notify the upper layer of the new cert 70 | _this.tlscert(_nodeForge.pki.certificateToPem(certs[0])); 71 | // fail when fingerprint does not match 72 | return false; 73 | }, 74 | connected: function connected(connection) { 75 | if (!connection) { 76 | _this.tlserror('Unable to connect'); 77 | _this.tlsclose(); 78 | return; 79 | } 80 | 81 | // tls connection open 82 | _this.open = true; 83 | 84 | _this.tlsopen(); 85 | 86 | // empty the buffer 87 | while (_this._outboundBuffer.length) { 88 | _this.prepareOutbound(_this._outboundBuffer.shift()); 89 | } 90 | }, 91 | tlsDataReady: function tlsDataReady(connection) { 92 | return _this.tlsoutbound(s2a(connection.tlsData.getBytes())); 93 | }, 94 | dataReady: function dataReady(connection) { 95 | return _this.tlsinbound(s2a(connection.data.getBytes())); 96 | }, 97 | closed: function closed() { 98 | return _this.tlsclose(); 99 | }, 100 | error: function error(connection, _error) { 101 | _this.tlserror(_error.message); 102 | _this.tlsclose(); 103 | } 104 | }); 105 | } 106 | 107 | _createClass(TlsClient, [{ 108 | key: 'configure', 109 | value: function configure(options) { 110 | this._host = options.host; 111 | if (options.ca) { 112 | this._ca = _nodeForge.pki.certificateFromPem(options.ca); 113 | } 114 | } 115 | }, { 116 | key: 'prepareOutbound', 117 | value: function prepareOutbound(buffer) { 118 | if (!this.open) { 119 | this._outboundBuffer.push(buffer); 120 | return; 121 | } 122 | 123 | this._tls.prepare(a2s(buffer)); 124 | } 125 | }, { 126 | key: 'processInbound', 127 | value: function processInbound(buffer) { 128 | this._tls.process(a2s(buffer)); 129 | } 130 | }, { 131 | key: 'handshake', 132 | value: function handshake() { 133 | this._tls.handshake(); 134 | } 135 | 136 | /** 137 | * Verifies a host name by the Common Name or Subject Alternative Names 138 | * Expose as a method of TlsClient for testing purposes 139 | * 140 | * @param {Object} cert A forge certificate object 141 | * @param {String} host The host name, e.g. imap.gmail.com 142 | * @return {Boolean} true, if host name matches certificate, otherwise false 143 | */ 144 | 145 | }, { 146 | key: 'verifyCertificate', 147 | value: function verifyCertificate(cert, host) { 148 | var _this2 = this; 149 | 150 | var entries = void 0; 151 | 152 | var subjectAltName = cert.getExtension({ 153 | name: 'subjectAltName' 154 | }); 155 | 156 | var cn = cert.subject.getField('CN'); 157 | 158 | // If subjectAltName is present then it must be used and Common Name must be discarded 159 | // http://tools.ietf.org/html/rfc2818#section-3.1 160 | // So we check subjectAltName first and if it does not exist then revert back to Common Name 161 | if (subjectAltName && subjectAltName.altNames && subjectAltName.altNames.length) { 162 | entries = subjectAltName.altNames.map(function (entry) { 163 | return entry.value; 164 | }); 165 | } else if (cn && cn.value) { 166 | entries = [cn.value]; 167 | } else { 168 | return false; 169 | } 170 | 171 | // find matches for hostname and if any are found return true, otherwise returns false 172 | return !!entries.filter(function (sanEntry) { 173 | return _this2.compareServername(host.toLowerCase(), sanEntry.toLowerCase()); 174 | }).length; 175 | } 176 | 177 | /** 178 | * Compares servername with a subjectAltName entry. Returns true if these values match. 179 | * 180 | * Wildcard usage in certificate hostnames is very limited, the only valid usage 181 | * form is "*.domain" and not "*sub.domain" or "sub.*.domain" so we only have to check 182 | * if the entry starts with "*." when comparing against a wildcard hostname. If "*" is used 183 | * in invalid places, then treat it as a string and not as a wildcard. 184 | * 185 | * @param {String} servername Hostname to check 186 | * @param {String} sanEntry subjectAltName entry to check against 187 | * @returns {Boolean} Returns true if hostname matches entry from SAN 188 | */ 189 | 190 | }, { 191 | key: 'compareServername', 192 | value: function compareServername() { 193 | var servername = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 194 | var sanEntry = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; 195 | 196 | // if the entry name does not include a wildcard, then expect exact match 197 | if (sanEntry.substr(0, 2) !== '*.') { 198 | return sanEntry === servername; 199 | } 200 | 201 | // otherwise ignore the first subdomain 202 | return servername.split('.').slice(1).join('.') === sanEntry.substr(2); 203 | } 204 | }]); 205 | 206 | return TlsClient; 207 | }(); 208 | 209 | exports.default = TlsClient; 210 | 211 | 212 | var a2s = function a2s(arr) { 213 | return String.fromCharCode.apply(null, new Uint8Array(arr)); 214 | }; 215 | var s2a = function s2a(str) { 216 | return new Uint8Array(str.split('').map(function (char) { 217 | return char.charCodeAt(0); 218 | })).buffer; 219 | }; 220 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy90bHMuanMiXSwibmFtZXMiOlsiVGxzQ2xpZW50Iiwib3BlbiIsIl9vdXRib3VuZEJ1ZmZlciIsIl90bHMiLCJjcmVhdGVDb25uZWN0aW9uIiwic2VydmVyIiwidmVyaWZ5IiwiY29ubmVjdGlvbiIsInZlcmlmaWVkIiwiZGVwdGgiLCJjZXJ0cyIsInZlcmlmeUNlcnRpZmljYXRlIiwiX2hvc3QiLCJfY2EiLCJ0bHNjZXJ0IiwiY2VydGlmaWNhdGVUb1BlbSIsImZwUGlubmVkIiwiZ2V0UHVibGljS2V5RmluZ2VycHJpbnQiLCJwdWJsaWNLZXkiLCJlbmNvZGluZyIsImZwUmVtb3RlIiwiY29ubmVjdGVkIiwidGxzZXJyb3IiLCJ0bHNjbG9zZSIsInRsc29wZW4iLCJsZW5ndGgiLCJwcmVwYXJlT3V0Ym91bmQiLCJzaGlmdCIsInRsc0RhdGFSZWFkeSIsInRsc291dGJvdW5kIiwiczJhIiwidGxzRGF0YSIsImdldEJ5dGVzIiwiZGF0YVJlYWR5IiwidGxzaW5ib3VuZCIsImRhdGEiLCJjbG9zZWQiLCJlcnJvciIsIm1lc3NhZ2UiLCJvcHRpb25zIiwiaG9zdCIsImNhIiwiY2VydGlmaWNhdGVGcm9tUGVtIiwiYnVmZmVyIiwicHVzaCIsInByZXBhcmUiLCJhMnMiLCJwcm9jZXNzIiwiaGFuZHNoYWtlIiwiY2VydCIsImVudHJpZXMiLCJzdWJqZWN0QWx0TmFtZSIsImdldEV4dGVuc2lvbiIsIm5hbWUiLCJjbiIsInN1YmplY3QiLCJnZXRGaWVsZCIsImFsdE5hbWVzIiwibWFwIiwiZW50cnkiLCJ2YWx1ZSIsImZpbHRlciIsImNvbXBhcmVTZXJ2ZXJuYW1lIiwidG9Mb3dlckNhc2UiLCJzYW5FbnRyeSIsInNlcnZlcm5hbWUiLCJzdWJzdHIiLCJzcGxpdCIsInNsaWNlIiwiam9pbiIsIlN0cmluZyIsImZyb21DaGFyQ29kZSIsImFwcGx5IiwiVWludDhBcnJheSIsImFyciIsInN0ciIsImNoYXIiLCJjaGFyQ29kZUF0Il0sIm1hcHBpbmdzIjoi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Q1YsUUFBUVcsTUFBUixDQUFlO0FBQUEsZUFBWSxPQUFLQyxpQkFBTCxDQUF1QnRCLEtBQUt1QixXQUFMLEVBQXZCLEVBQTJDQyxTQUFTRCxXQUFULEVBQTNDLENBQVo7QUFBQSxPQUFmLEVBQStGdEMsTUFBeEc7QUFDRDs7QUFFRDs7Ozs7Ozs7Ozs7Ozs7O3dDQVltRDtBQUFBLFVBQWhDd0MsVUFBZ0MsdUVBQW5CLEVBQW1CO0FBQUEsVUFBZkQsUUFBZSx1RUFBSixFQUFJOztBQUNqRDtBQUNBLFVBQUlBLFNBQVNFLE1BQVQsQ0FBZ0IsQ0FBaEIsRUFBbUIsQ0FBbkIsTUFBMEIsSUFBOUIsRUFBb0M7QUFDbEMsZUFBT0YsYUFBYUMsVUFBcEI7QUFDRDs7QUFFRDtBQUNBLGFBQU9BLFdBQVdFLEtBQVgsQ0FBaUIsR0FBakIsRUFBc0JDLEtBQXRCLENBQTRCLENBQTVCLEVBQStCQyxJQUEvQixDQUFvQyxHQUFwQyxNQUE2Q0wsU0FBU0UsTUFBVCxDQUFnQixDQUFoQixDQUFwRDtBQUNEOzs7Ozs7a0JBbEtrQmxFLFM7OztBQXFLckIsSUFBTThDLE1BQU0sU0FBTkEsR0FBTTtBQUFBLFNBQU93QixPQUFPQyxZQUFQLENBQW9CQyxLQUFwQixDQUEwQixJQUExQixFQUFnQyxJQUFJQyxVQUFKLENBQWVDLEdBQWYsQ0FBaEMsQ0FBUDtBQUFBLENBQVo7QUFDQSxJQUFNNUMsTUFBTSxTQUFOQSxHQUFNO0FBQUEsU0FBTyxJQUFJMkMsVUFBSixDQUFlRSxJQUFJUixLQUFKLENBQVUsRUFBVixFQUFjVCxHQUFkLENBQWtCO0FBQUEsV0FBUWtCLEtBQUtDLFVBQUwsQ0FBZ0IsQ0FBaEIsQ0FBUjtBQUFBLEdBQWxCLENBQWYsRUFBOERsQyxNQUFyRTtBQUFBLENBQVoiLCJmaWxlIjoidGxzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgdGxzLCBwa2kgfSBmcm9tICdub2RlLWZvcmdlJ1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBUbHNDbGllbnQge1xuICBjb25zdHJ1Y3RvciAoKSB7XG4gICAgdGhpcy5vcGVuID0gZmFsc2VcbiAgICB0aGlzLl9vdXRib3VuZEJ1ZmZlciA9IFtdXG5cbiAgICB0aGlzLl90bHMgPSB0bHMuY3JlYXRlQ29ubmVjdGlvbih7XG4gICAgICBzZXJ2ZXI6IGZhbHNlLFxuICAgICAgdmVyaWZ5OiAoY29ubmVjdGlvbiwgdmVyaWZpZWQsIGRlcHRoLCBjZXJ0cykgPT4ge1xuICAgICAgICBpZiAoIShjZXJ0cyAmJiBjZXJ0c1swXSkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy52ZXJpZnlDZXJ0aWZpY2F0ZShjZXJ0c1swXSwgdGhpcy5faG9zdCkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgICAgfVxuXG4gICAgICAgIC8qXG4gICAgICAgICAqIFBsZWFzZSBzZWUgdGhlIHJlYWRtZSBmb3IgYW4gZXhwbGFuYXRpb24gb2YgdGhlIGJlaGF2aW9yIHdpdGhvdXQgYSBuYXRpdmUgVExTIHN0YWNrIVxuICAgICAgICAgKi9cblxuICAgICAgICAvLyB3aXRob3V0IGEgcGlubmVkIGNlcnRpZmljYXRlLCB3ZSdsbCBqdXN0IGFjY2VwdCB0aGUgY29ubmVjdGlvbiBhbmQgbm90aWZ5IHRoZSB1cHBlciBsYXllclxuICAgICAgICBpZiAoIXRoaXMuX2NhKSB7XG4gICAgICAgICAgLy8gbm90aWZ5IHRoZSB1cHBlciBsYXllciBvZiB0aGUgbmV3IGNlcnRcbiAgICAgICAgICB0aGlzLnRsc2NlcnQocGtpLmNlcnRpZmljYXRlVG9QZW0oY2VydHNbMF0pKVxuICAgICAgICAgIC8vIHN1Y2NlZWQgb25seSBpZiB0aGlzLnRsc2NlcnQgaXMgaW1wbGVtZW50ZWQgKG90aGVyd2lzZSBmb3JnZSBjYXRjaGVzIHRoZSBlcnJvcilcbiAgICAgICAgICByZXR1cm4gdHJ1ZVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gaWYgd2UgaGF2ZSBhIHBpbm5lZCBjZXJ0aWZpY2F0ZSwgdGhpbmdzIGdldCBhIGxpdHRsZSBtb3JlIGNvbXBsaWNhdGVkOlxuICAgICAgICAvLyAtIGxlYWYgY2VydGlmaWNhdGVzIHBpbiB0aGUgaG9zdCBkaXJlY3RseSwgZS5nLiBmb3Igc2VsZi1zaWduZWQgY2VydGlmaWNhdGVzXG4gICAgICAgIC8vIC0gd2UgYWxzbyBhbGxvdyBpbnRlcm1lZGlhdGUgY2VydGlmaWNhdGVzLCBmb3IgcHJvdmlkZXJzIHRoYXQgYXJlIGFibGUgdG8gc2lnbiB0aGVpciBvd24gY2VydHMuXG5cbiAgICAgICAgLy8gZGV0ZWN0IGlmIHRoaXMgaXMgYSBjZXJ0aWZpY2F0ZSB1c2VkIGZvciBzaWduaW5nIGJ5IHRlc3RpbmcgaWYgdGhlIGNvbW1vbiBuYW1lIGRpZmZlcmVudCBmcm9tIHRoZSBob3N0bmFtZS5cbiAgICAgICAgLy8gYWxzbywgYW4gaW50ZXJtZWRpYXRlIGNlcnQgaGFzIG5vIFNBTnMsIGF0IGxlYXN0IG5vbmUgdGhhdCBtYXRjaCB0aGUgaG9zdG5hbWUuXG4gICAgICAgIGlmICghdGhpcy52ZXJpZnlDZXJ0aWZpY2F0ZSh0aGlzLl9jYSwgdGhpcy5faG9zdCkpIHtcbiAgICAgICAgICAvLyB2ZXJpZnkgY2VydGlmaWNhdGUgdGhyb3VnaCBhIHZhbGlkIGNlcnRpZmljYXRlIGNoYWluXG4gICAgICAgICAgcmV0dXJuIHRoaXMuX2NhLnZlcmlmeShjZXJ0c1swXSlcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHZlcmlmeSBjZXJ0aWZpY2F0ZSB0aHJvdWdoIGhvc3QgY2VydGlmaWNhdGUgcGlubmluZ1xuICAgICAgICB2YXIgZnBQaW5uZWQgPSBwa2kuZ2V0UHVibGljS2V5RmluZ2VycHJpbnQodGhpcy5fY2EucHVibGljS2V5LCB7XG4gICAgICAgICAgZW5jb2Rpbmc6ICdoZXgnXG4gICAgICAgIH0pXG4gICAgICAgIHZhciBmcFJlbW90ZSA9IHBraS5nZXRQdWJsaWNLZXlGaW5nZXJwcmludChjZXJ0c1swXS5wdWJsaWNLZXksIHtcbiAgICAgICAgICBlbmNvZGluZzogJ2hleCdcbiAgICAgICAgfSlcblxuICAgICAgICAvLyBjaGVjayBpZiBjZXJ0IGZpbmdlcnByaW50cyBtYXRjaFxuICAgICAgICBpZiAoZnBQaW5uZWQgPT09IGZwUmVtb3RlKSB7XG4gICAgICAgICAgcmV0dXJuIHRydWVcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIG5vdGlmeSB0aGUgdXBwZXIgbGF5ZXIgb2YgdGhlIG5ldyBjZXJ0XG4gICAgICAgIHRoaXMudGxzY2VydChwa2kuY2VydGlmaWNhdGVUb1BlbShjZXJ0c1swXSkpXG4gICAgICAgIC8vIGZhaWwgd2hlbiBmaW5nZXJwcmludCBkb2VzIG5vdCBtYXRjaFxuICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgIH0sXG4gICAgICBjb25uZWN0ZWQ6IChjb25uZWN0aW9uKSA9PiB7XG4gICAgICAgIGlmICghY29ubmVjdGlvbikge1xuICAgICAgICAgIHRoaXMudGxzZXJyb3IoJ1VuYWJsZSB0byBjb25uZWN0JylcbiAgICAgICAgICB0aGlzLnRsc2Nsb3NlKClcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHRscyBjb25uZWN0aW9uIG9wZW5cbiAgICAgICAgdGhpcy5vcGVuID0gdHJ1ZVxuXG4gICAgICAgIHRoaXMudGxzb3BlbigpXG5cbiAgICAgICAgLy8gZW1wdHkgdGhlIGJ1ZmZlclxuICAgICAgICB3aGlsZSAodGhpcy5fb3V0Ym91bmRCdWZmZXIubGVuZ3RoKSB7XG4gICAgICAgICAgdGhpcy5wcmVwYXJlT3V0Ym91bmQodGhpcy5fb3V0Ym91bmRCdWZmZXIuc2hpZnQoKSlcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHRsc0RhdGFSZWFkeTogKGNvbm5lY3Rpb24pID0+IHRoaXMudGxzb3V0Ym91bmQoczJhKGNvbm5lY3Rpb24udGxzRGF0YS5nZXRCeXRlcygpKSksXG4gICAgICBkYXRhUmVhZHk6IChjb25uZWN0aW9uKSA9PiB0aGlzLnRsc2luYm91bmQoczJhKGNvbm5lY3Rpb24uZGF0YS5nZXRCeXRlcygpKSksXG4gICAgICBjbG9zZWQ6ICgpID0+IHRoaXMudGxzY2xvc2UoKSxcbiAgICAgIGVycm9yOiAoY29ubmVjdGlvbiwgZXJyb3IpID0+IHtcbiAgICAgICAgdGhpcy50bHNlcnJvcihlcnJvci5tZXNzYWdlKVxuICAgICAgICB0aGlzLnRsc2Nsb3NlKClcbiAgICAgIH1cbiAgICB9KVxuICB9XG5cbiAgY29uZmlndXJlIChvcHRpb25zKSB7XG4gICAgdGhpcy5faG9zdCA9IG9wdGlvbnMuaG9zdFxuICAgIGlmIChvcHRpb25zLmNhKSB7XG4gICAgICB0aGlzLl9jYSA9IHBraS5jZXJ0aWZpY2F0ZUZyb21QZW0ob3B0aW9ucy5jYSlcbiAgICB9XG4gIH1cblxuICBwcmVwYXJlT3V0Ym91bmQgKGJ1ZmZlcikge1xuICAgIGlmICghdGhpcy5vcGVuKSB7XG4gICAgICB0aGlzLl9vdXRib3VuZEJ1ZmZlci5wdXNoKGJ1ZmZlcilcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHRoaXMuX3Rscy5wcmVwYXJlKGEycyhidWZmZXIpKVxuICB9XG5cbiAgcHJvY2Vzc0luYm91bmQgKGJ1ZmZlcikge1xuICAgIHRoaXMuX3Rscy5wcm9jZXNzKGEycyhidWZmZXIpKVxuICB9XG5cbiAgaGFuZHNoYWtlICgpIHtcbiAgICB0aGlzLl90bHMuaGFuZHNoYWtlKClcbiAgfVxuXG4gIC8qKlxuICAgKiBWZXJpZmllcyBhIGhvc3QgbmFtZSBieSB0aGUgQ29tbW9uIE5hbWUgb3IgU3ViamVjdCBBbHRlcm5hdGl2ZSBOYW1lc1xuICAgKiBFeHBvc2UgYXMgYSBtZXRob2Qgb2YgVGxzQ2xpZW50IGZvciB0ZXN0aW5nIHB1cnBvc2VzXG4gICAqXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBjZXJ0IEEgZm9yZ2UgY2VydGlmaWNhdGUgb2JqZWN0XG4gICAqIEBwYXJhbSB7U3RyaW5nfSBob3N0IFRoZSBob3N0IG5hbWUsIGUuZy4gaW1hcC5nbWFpbC5jb21cbiAgICogQHJldHVybiB7Qm9vbGVhbn0gdHJ1ZSwgaWYgaG9zdCBuYW1lIG1hdGNoZXMgY2VydGlmaWNhdGUsIG90aGVyd2lzZSBmYWxzZVxuICAgKi9cbiAgdmVyaWZ5Q2VydGlmaWNhdGUgKGNlcnQsIGhvc3QpIHtcbiAgICBsZXQgZW50cmllc1xuXG4gICAgY29uc3Qgc3ViamVjdEFsdE5hbWUgPSBjZXJ0LmdldEV4dGVuc2lvbih7XG4gICAgICBuYW1lOiAnc3ViamVjdEFsdE5hbWUnXG4gICAgfSlcblxuICAgIGNvbnN0IGNuID0gY2VydC5zdWJqZWN0LmdldEZpZWxkKCdDTicpXG5cbiAgICAvLyBJZiBzdWJqZWN0QWx0TmFtZSBpcyBwcmVzZW50IHRoZW4gaXQgbXVzdCBiZSB1c2VkIGFuZCBDb21tb24gTmFtZSBtdXN0IGJlIGRpc2NhcmRlZFxuICAgIC8vIGh0dHA6Ly90b29scy5pZXRmLm9yZy9odG1sL3JmYzI4MTgjc2VjdGlvbi0zLjFcbiAgICAvLyBTbyB3ZSBjaGVjayBzdWJqZWN0QWx0TmFtZSBmaXJzdCBhbmQgaWYgaXQgZG9lcyBub3QgZXhpc3QgdGhlbiByZXZlcnQgYmFjayB0byBDb21tb24gTmFtZVxuICAgIGlmIChzdWJqZWN0QWx0TmFtZSAmJiBzdWJqZWN0QWx0TmFtZS5hbHROYW1lcyAmJiBzdWJqZWN0QWx0TmFtZS5hbHROYW1lcy5sZW5ndGgpIHtcbiAgICAgIGVudHJpZXMgPSBzdWJqZWN0QWx0TmFtZS5hbHROYW1lcy5tYXAoZnVuY3Rpb24gKGVudHJ5KSB7XG4gICAgICAgIHJldHVybiBlbnRyeS52YWx1ZVxuICAgICAgfSlcbiAgICB9IGVsc2UgaWYgKGNuICYmIGNuLnZhbHVlKSB7XG4gICAgICBlbnRyaWVzID0gW2NuLnZhbHVlXVxuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG5cbiAgICAvLyBmaW5kIG1hdGNoZXMgZm9yIGhvc3RuYW1lIGFuZCBpZiBhbnkgYXJlIGZvdW5kIHJldHVybiB0cnVlLCBvdGhlcndpc2UgcmV0dXJucyBmYWxzZVxuICAgIHJldHVybiAhIWVudHJpZXMuZmlsdGVyKHNhbkVudHJ5ID0+IHRoaXMuY29tcGFyZVNlcnZlcm5hbWUoaG9zdC50b0xvd2VyQ2FzZSgpLCBzYW5FbnRyeS50b0xvd2VyQ2FzZSgpKSkubGVuZ3RoXG4gIH1cblxuICAvKipcbiAgICogQ29tcGFyZXMgc2VydmVybmFtZSB3aXRoIGEgc3ViamVjdEFsdE5hbWUgZW50cnkuIFJldHVybnMgdHJ1ZSBpZiB0aGVzZSB2YWx1ZXMgbWF0Y2guXG4gICAqXG4gICAqIFdpbGRjYXJkIHVzYWdlIGluIGNlcnRpZmljYXRlIGhvc3RuYW1lcyBpcyB2ZXJ5IGxpbWl0ZWQsIHRoZSBvbmx5IHZhbGlkIHVzYWdlXG4gICAqIGZvcm0gaXMgXCIqLmRvbWFpblwiIGFuZCBub3QgXCIqc3ViLmRvbWFpblwiIG9yIFwic3ViLiouZG9tYWluXCIgc28gd2Ugb25seSBoYXZlIHRvIGNoZWNrXG4gICAqIGlmIHRoZSBlbnRyeSBzdGFydHMgd2l0aCBcIiouXCIgd2hlbiBjb21wYXJpbmcgYWdhaW5zdCBhIHdpbGRjYXJkIGhvc3RuYW1lLiBJZiBcIipcIiBpcyB1c2VkXG4gICAqIGluIGludmFsaWQgcGxhY2VzLCB0aGVuIHRyZWF0IGl0IGFzIGEgc3RyaW5nIGFuZCBub3QgYXMgYSB3aWxkY2FyZC5cbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHNlcnZlcm5hbWUgSG9zdG5hbWUgdG8gY2hlY2tcbiAgICogQHBhcmFtIHtTdHJpbmd9IHNhbkVudHJ5IHN1YmplY3RBbHROYW1lIGVudHJ5IHRvIGNoZWNrIGFnYWluc3RcbiAgICogQHJldHVybnMge0Jvb2xlYW59IFJldHVybnMgdHJ1ZSBpZiBob3N0bmFtZSBtYXRjaGVzIGVudHJ5IGZyb20gU0FOXG4gICAqL1xuICBjb21wYXJlU2VydmVybmFtZSAoc2VydmVybmFtZSA9ICcnLCBzYW5FbnRyeSA9ICcnKSB7XG4gICAgLy8gaWYgdGhlIGVudHJ5IG5hbWUgZG9lcyBub3QgaW5jbHVkZSBhIHdpbGRjYXJkLCB0aGVuIGV4cGVjdCBleGFjdCBtYXRjaFxuICAgIGlmIChzYW5FbnRyeS5zdWJzdHIoMCwgMikgIT09ICcqLicpIHtcbiAgICAgIHJldHVybiBzYW5FbnRyeSA9PT0gc2VydmVybmFtZVxuICAgIH1cblxuICAgIC8vIG90aGVyd2lzZSBpZ25vcmUgdGhlIGZpcnN0IHN1YmRvbWFpblxuICAgIHJldHVybiBzZXJ2ZXJuYW1lLnNwbGl0KCcuJykuc2xpY2UoMSkuam9pbignLicpID09PSBzYW5FbnRyeS5zdWJzdHIoMilcbiAgfVxufVxuXG5jb25zdCBhMnMgPSBhcnIgPT4gU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShudWxsLCBuZXcgVWludDhBcnJheShhcnIpKVxuY29uc3QgczJhID0gc3RyID0+IG5ldyBVaW50OEFycmF5KHN0ci5zcGxpdCgnJykubWFwKGNoYXIgPT4gY2hhci5jaGFyQ29kZUF0KDApKSkuYnVmZmVyXG4iXX0= -------------------------------------------------------------------------------- /dist/windows-socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _ramda = require('ramda'); 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 12 | 13 | var TCPSocket = function () { 14 | _createClass(TCPSocket, null, [{ 15 | key: 'open', 16 | value: function open(host, port) { 17 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 18 | 19 | return new TCPSocket({ host: host, port: port, options: options }); 20 | } 21 | }]); 22 | 23 | function TCPSocket(_ref) { 24 | var _this = this; 25 | 26 | var host = _ref.host, 27 | port = _ref.port, 28 | options = _ref.options; 29 | 30 | _classCallCheck(this, TCPSocket); 31 | 32 | this.host = new Windows.Networking.HostName(host); // NB! HostName constructor will throw on invalid input 33 | this.port = port; 34 | this.ssl = (0, _ramda.propOr)(false, 'useSecureTransport')(options); 35 | this.bufferedAmount = 0; 36 | this.readyState = 'connecting'; 37 | this.binaryType = (0, _ramda.propOr)('arraybuffer', 'binaryType')(options); 38 | 39 | if (this.binaryType !== 'arraybuffer') { 40 | throw new Error('Only arraybuffers are supported!'); 41 | } 42 | 43 | this._socket = new Windows.Networking.Sockets.StreamSocket(); 44 | 45 | this._socket.control.keepAlive = true; 46 | this._socket.control.noDelay = true; 47 | 48 | this._dataReader = null; 49 | this._dataWriter = null; 50 | 51 | // set to true if upgrading with STARTTLS 52 | this._upgrading = false; 53 | 54 | // cache all client.send calls to this array if currently upgrading 55 | this._upgradeCache = []; 56 | 57 | // initial socket type. default is 'plainSocket' (no encryption applied) 58 | // 'tls12' supports the TLS 1.2, TLS 1.1 and TLS 1.0 protocols but no SSL 59 | this._protectionLevel = Windows.Networking.Sockets.SocketProtectionLevel[this.ssl ? 'tls12' : 'plainSocket']; 60 | 61 | // Initiate connection to destination 62 | this._socket.connectAsync(this.host, this.port, this._protectionLevel).done(function () { 63 | _this._setStreamHandlers(); 64 | _this._emit('open'); 65 | }, function (e) { 66 | return _this._emit('error', e); 67 | }); 68 | } 69 | 70 | /** 71 | * Initiate Reader and Writer interfaces for the socket 72 | */ 73 | 74 | 75 | _createClass(TCPSocket, [{ 76 | key: '_setStreamHandlers', 77 | value: function _setStreamHandlers() { 78 | this._dataReader = new Windows.Storage.Streams.DataReader(this._socket.inputStream); 79 | this._dataReader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial; 80 | 81 | // setup writer 82 | this._dataWriter = new Windows.Storage.Streams.DataWriter(this._socket.outputStream); 83 | 84 | // start byte reader loop 85 | this._read(); 86 | } 87 | 88 | /** 89 | * Emit an error and close socket 90 | * 91 | * @param {Error} error Error object 92 | */ 93 | 94 | }, { 95 | key: '_errorHandler', 96 | value: function _errorHandler(error) { 97 | // we ignore errors after close has been called, since all aborted operations 98 | // will emit their error handlers 99 | // this will also apply to starttls as a read call is aborted before upgrading the socket 100 | if (this._upgrading || this.readyState !== 'closing' && this.readyState !== 'closed') { 101 | this._emit('error', error); 102 | this.close(); 103 | } 104 | } 105 | 106 | /** 107 | * Read available bytes from the socket. This method is recursive once it ends, it restarts itthis 108 | */ 109 | 110 | }, { 111 | key: '_read', 112 | value: function _read() { 113 | var _this2 = this; 114 | 115 | if (this._upgrading || this.readyState !== 'open' && this.readyState !== 'connecting') { 116 | return; // do nothing if socket not open 117 | } 118 | 119 | // Read up to 4096 bytes from the socket. This is not a fixed number (the mode was set 120 | // with inputStreamOptions.partial property), so it might return with a smaller 121 | // amount of bytes. 122 | this._dataReader.loadAsync(4096).done(function (availableByteCount) { 123 | if (!availableByteCount) { 124 | // no bytes available for reading, restart the reading process 125 | return setImmediate(_this2._read.bind(_this2)); 126 | } 127 | 128 | // we need an Uint8Array that gets filled with the bytes from the buffer 129 | var data = new Uint8Array(availableByteCount); 130 | _this2._dataReader.readBytes(data); // data argument gets filled with the bytes 131 | 132 | _this2._emit('data', data.buffer); 133 | 134 | // restart reading process 135 | return setImmediate(_this2._read.bind(_this2)); 136 | }, function (e) { 137 | return _this2._errorHandler(e); 138 | }); 139 | } 140 | 141 | // 142 | // API 143 | // 144 | 145 | }, { 146 | key: 'close', 147 | value: function close() { 148 | this.readyState = 'closing'; 149 | 150 | try { 151 | this._socket.close(); 152 | } catch (E) { 153 | this._emit('error', E); 154 | } 155 | 156 | setImmediate(this._emit.bind(this, 'close')); 157 | } 158 | }, { 159 | key: 'send', 160 | value: function send(data) { 161 | var _this3 = this; 162 | 163 | if (this.readyState !== 'open') { 164 | return; 165 | } 166 | 167 | if (this._upgrading) { 168 | this._upgradeCache.push(data); 169 | return; 170 | } 171 | 172 | // Write bytes to buffer 173 | this._dataWriter.writeBytes(data); 174 | 175 | // Emit buffer contents 176 | this._dataWriter.storeAsync().done(function () { 177 | return _this3._emit('drain'); 178 | }, function (e) { 179 | return _this3._errorHandler(e); 180 | }); 181 | } 182 | }, { 183 | key: 'upgradeToSecure', 184 | value: function upgradeToSecure() { 185 | var _this4 = this; 186 | 187 | if (this.ssl || this._upgrading) return; 188 | 189 | this._upgrading = true; 190 | try { 191 | // release current input stream. this is required to allow socket upgrade 192 | // write stream is not released as all send calls are cached from this point onwards 193 | // and not passed to socket until the socket is upgraded 194 | this._dataReader.detachStream(); 195 | } catch (E) {} 196 | 197 | // update protection level 198 | this._protectionLevel = Windows.Networking.Sockets.SocketProtectionLevel.tls12; 199 | 200 | this._socket.upgradeToSslAsync(this._protectionLevel, this.host).done(function () { 201 | _this4._upgrading = false; 202 | _this4.ssl = true; // secured connection from now on 203 | 204 | _this4._dataReader = new Windows.Storage.Streams.DataReader(_this4._socket.inputStream); 205 | _this4._dataReader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial; 206 | _this4._read(); 207 | 208 | // emit all cached requests 209 | while (_this4._upgradeCache.length) { 210 | var data = _this4._upgradeCache.shift(); 211 | _this4.send(data); 212 | } 213 | }, function (e) { 214 | _this4._upgrading = false; 215 | _this4._errorHandler(e); 216 | }); 217 | } 218 | }, { 219 | key: '_emit', 220 | value: function _emit(type, data) { 221 | var target = this; 222 | switch (type) { 223 | case 'open': 224 | this.readyState = 'open'; 225 | this.onopen && this.onopen({ target: target, type: type, data: data }); 226 | break; 227 | case 'error': 228 | this.onerror && this.onerror({ target: target, type: type, data: data }); 229 | break; 230 | case 'data': 231 | this.ondata && this.ondata({ target: target, type: type, data: data }); 232 | break; 233 | case 'drain': 234 | this.ondrain && this.ondrain({ target: target, type: type, data: data }); 235 | break; 236 | case 'close': 237 | this.readyState = 'closed'; 238 | this.onclose && this.onclose({ target: target, type: type, data: data }); 239 | break; 240 | } 241 | } 242 | }]); 243 | 244 | return TCPSocket; 245 | }(); 246 | 247 | exports.default = TCPSocket; 248 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy93aW5kb3dzLXNvY2tldC5qcyJdLCJuYW1lcyI6WyJUQ1BTb2NrZXQiLCJob3N0IiwicG9ydCIsIm9wdGlvbnMiLCJXaW5kb3dzIiwiTmV0d29ya2luZyIsIkhvc3ROYW1lIiwic3NsIiwiYnVmZmVyZWRBbW91bnQiLCJyZWFkeVN0YXRlIiwiYmluYXJ5VHlwZSIsIkVycm9yIiwiX3NvY2tldCIsIlNvY2tldHMiLCJTdHJlYW1Tb2NrZXQiLCJjb250cm9sIiwia2VlcEFsaXZlIiwibm9EZWxheSIsIl9kYXRhUmVhZGVyIiwiX2RhdGFXcml0ZXIiLCJfdXBncmFkaW5nIiwiX3VwZ3JhZGVDYWNoZSIsIl9wcm90ZWN0aW9uTGV2ZWwiLCJTb2NrZXRQcm90ZWN0aW9uTGV2ZWwiLCJjb25uZWN0QXN5bmMiLCJkb25lIiwiX3NldFN0cmVhbUhhbmRsZXJzIiwiX2VtaXQiLCJlIiwiU3RvcmFnZSIsIlN0cmVhbXMiLCJEYXRhUmVhZGVyIiwiaW5wdXRTdHJlYW0iLCJpbnB1dFN0cmVhbU9wdGlvbnMiLCJJbnB1dFN0cmVhbU9wdGlvbnMiLCJwYXJ0aWFsIiwiRGF0YVdyaXRlciIsIm91dHB1dFN0cmVhbSIsIl9yZWFkIiwiZXJyb3IiLCJjbG9zZSIsImxvYWRBc3luYyIsImF2YWlsYWJsZUJ5dGVDb3VudCIsInNldEltbWVkaWF0ZSIsImJpbmQiLCJkYXRhIiwiVWludDhBcnJheSIsInJlYWRCeXRlcyIsImJ1ZmZlciIsIl9lcnJvckhhbmRsZXIiLCJFIiwicHVzaCIsIndyaXRlQnl0ZXMiLCJzdG9yZUFzeW5jIiwiZGV0YWNoU3RyZWFtIiwidGxzMTIiLCJ1cGdyYWRlVG9Tc2xBc3luYyIsImxlbmd0aCIsInNoaWZ0Iiwic2VuZCIsInR5cGUiLCJ0YXJnZXQiLCJvbm9wZW4iLCJvbmVycm9yIiwib25kYXRhIiwib25kcmFpbiIsIm9uY2xvc2UiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBQUE7Ozs7SUFFcUJBLFM7Ozt5QkFDTkMsSSxFQUFNQyxJLEVBQW9CO0FBQUEsVUFBZEMsT0FBYyx1RUFBSixFQUFJOztBQUNyQyxhQUFPLElBQUlILFNBQUosQ0FBYyxFQUFFQyxVQUFGLEVBQVFDLFVBQVIsRUFBY0MsZ0JBQWQsRUFBZCxDQUFQO0FBQ0Q7OztBQUVELDJCQUFzQztBQUFBOztBQUFBLFFBQXZCRixJQUF1QixRQUF2QkEsSUFBdUI7QUFBQSxRQUFqQkMsSUFBaUIsUUFBakJBLElBQWlCO0FBQUEsUUFBWEMsT0FBVyxRQUFYQSxPQUFXOztBQUFBOztBQUNwQyxTQUFLRixJQUFMLEdBQVksSUFBSUcsUUFBUUMsVUFBUixDQUFtQkMsUUFBdkIsQ0FBZ0NMLElBQWhDLENBQVosQ0FEb0MsQ0FDYztBQUNsRCxTQUFLQyxJQUFMLEdBQVlBLElBQVo7QUFDQSxTQUFLSyxHQUFMLEdBQVcsbUJBQU8sS0FBUCxFQUFjLG9CQUFkLEVBQW9DSixPQUFwQyxDQUFYO0FBQ0EsU0FBS0ssY0FBTCxHQUFzQixDQUF0QjtBQUNBLFNBQUtDLFVBQUwsR0FBa0IsWUFBbEI7QUFDQSxTQUFLQyxVQUFMLEdBQWtCLG1CQUFPLGFBQVAsRUFBc0IsWUFBdEIsRUFBb0NQLE9BQXBDLENBQWxCOztBQUVBLFFBQUksS0FBS08sVUFBTCxLQUFvQixhQUF4QixFQUF1QztBQUNyQyxZQUFNLElBQUlDLEtBQUosQ0FBVSxrQ0FBVixDQUFOO0FBQ0Q7O0FBRUQsU0FBS0MsT0FBTCxHQUFlLElBQUlSLFFBQVFDLFVBQVIsQ0FBbUJRLE9BQW5CLENBQTJCQyxZQUEvQixFQUFmOztBQUVBLFNBQUtGLE9BQUwsQ0FBYUcsT0FBYixDQUFxQkMsU0FBckIsR0FBaUMsSUFBakM7QUFDQSxTQUFLSixPQUFMLENBQWFHLE9BQWIsQ0FBcUJFLE9BQXJCLEdBQStCLElBQS9COztBQUVBLFNBQUtDLFdBQUwsR0FBbUIsSUFBbkI7QUFDQSxTQUFLQyxXQUFMLEdBQW1CLElBQW5COztBQUVBO0FBQ0EsU0FBS0MsVUFBTCxHQUFrQixLQUFsQjs7QUFFQTtBQUNBLFNBQUtDLGFBQUwsR0FBcUIsRUFBckI7O0FBRUE7QUFDQTtBQUNBLFNBQUtDLGdCQUFMLEdBQXdCbEIsUUFBUUMsVUFBUixDQUFtQlEsT0FBbkIsQ0FBMkJVLHFCQUEzQixDQUFpRCxLQUFLaEIsR0FBTCxHQUFXLE9BQVgsR0FBcUIsYUFBdEUsQ0FBeEI7O0FBRUE7QUFDQSxTQUFLSyxPQUFMLENBQ0dZLFlBREgsQ0FDZ0IsS0FBS3ZCLElBRHJCLEVBQzJCLEtBQUtDLElBRGhDLEVBQ3NDLEtBQUtvQixnQkFEM0MsRUFFR0csSUFGSCxDQUVRLFlBQU07QUFDVixZQUFLQyxrQkFBTDtBQUNBLFlBQUtDLEtBQUwsQ0FBVyxNQUFYO0FBQ0QsS0FMSCxFQUtLO0FBQUEsYUFBSyxNQUFLQSxLQUFMLENBQVcsT0FBWCxFQUFvQkMsQ0FBcEIsQ0FBTDtBQUFBLEtBTEw7QUFNRDs7QUFFRDs7Ozs7Ozt5Q0FHc0I7QUFDcEIsV0FBS1YsV0FBTCxHQUFtQixJQUFJZCxRQUFReUIsT0FBUixDQUFnQkMsT0FBaEIsQ0FBd0JDLFVBQTVCLENBQXVDLEtBQUtuQixPQUFMLENBQWFvQixXQUFwRCxDQUFuQjtBQUNBLFdBQUtkLFdBQUwsQ0FBaUJlLGtCQUFqQixHQUFzQzdCLFFBQVF5QixPQUFSLENBQWdCQyxPQUFoQixDQUF3Qkksa0JBQXhCLENBQTJDQyxPQUFqRjs7QUFFQTtBQUNBLFdBQUtoQixXQUFMLEdBQW1CLElBQUlmLFFBQVF5QixPQUFSLENBQWdCQyxPQUFoQixDQUF3Qk0sVUFBNUIsQ0FBdUMsS0FBS3hCLE9BQUwsQ0FBYXlCLFlBQXBELENBQW5COztBQUVBO0FBQ0EsV0FBS0MsS0FBTDtBQUNEOztBQUVEOzs7Ozs7OztrQ0FLZUMsSyxFQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBLFVBQUksS0FBS25CLFVBQUwsSUFBb0IsS0FBS1gsVUFBTCxLQUFvQixTQUFwQixJQUFpQyxLQUFLQSxVQUFMLEtBQW9CLFFBQTdFLEVBQXdGO0FBQ3RGLGFBQUtrQixLQUFMLENBQVcsT0FBWCxFQUFvQlksS0FBcEI7QUFDQSxhQUFLQyxLQUFMO0FBQ0Q7QUFDRjs7QUFFRDs7Ozs7OzRCQUdTO0FBQUE7O0FBQ1AsVUFBSSxLQUFLcEIsVUFBTCxJQUFvQixLQUFLWCxVQUFMLEtBQW9CLE1BQXBCLElBQThCLEtBQUtBLFVBQUwsS0FBb0IsWUFBMUUsRUFBeUY7QUFDdkYsZUFEdUYsQ0FDaEY7QUFDUjs7QUFFRDtBQUNBO0FBQ0E7QUFDQSxXQUFLUyxXQUFMLENBQWlCdUIsU0FBakIsQ0FBMkIsSUFBM0IsRUFBaUNoQixJQUFqQyxDQUFzQyw4QkFBc0I7QUFDMUQsWUFBSSxDQUFDaUIsa0JBQUwsRUFBeUI7QUFDdkI7QUFDQSxpQkFBT0MsYUFBYSxPQUFLTCxLQUFMLENBQVdNLElBQVgsUUFBYixDQUFQO0FBQ0Q7O0FBRUQ7QUFDQSxZQUFJQyxPQUFPLElBQUlDLFVBQUosQ0FBZUosa0JBQWYsQ0FBWDtBQUNBLGVBQUt4QixXQUFMLENBQWlCNkIsU0FBakIsQ0FBMkJGLElBQTNCLEVBUjBELENBUXpCOztBQUVqQyxlQUFLbEIsS0FBTCxDQUFXLE1BQVgsRUFBbUJrQixLQUFLRyxNQUF4Qjs7QUFFQTtBQUNBLGVBQU9MLGFBQWEsT0FBS0wsS0FBTCxDQUFXTSxJQUFYLFFBQWIsQ0FBUDtBQUNELE9BZEQsRUFjRztBQUFBLGVBQUssT0FBS0ssYUFBTCxDQUFtQnJCLENBQW5CLENBQUw7QUFBQSxPQWRIO0FBZUQ7O0FBRUQ7QUFDQTtBQUNBOzs7OzRCQUVTO0FBQ1AsV0FBS25CLFVBQUwsR0FBa0IsU0FBbEI7O0FBRUEsVUFBSTtBQUNGLGFBQUtHLE9BQUwsQ0FBYTRCLEtBQWI7QUFDRCxPQUZELENBRUUsT0FBT1UsQ0FBUCxFQUFVO0FBQ1YsYUFBS3ZCLEtBQUwsQ0FBVyxPQUFYLEVBQW9CdUIsQ0FBcEI7QUFDRDs7QUFFRFAsbUJBQWEsS0FBS2hCLEtBQUwsQ0FBV2lCLElBQVgsQ0FBZ0IsSUFBaEIsRUFBc0IsT0FBdEIsQ0FBYjtBQUNEOzs7eUJBRUtDLEksRUFBTTtBQUFBOztBQUNWLFVBQUksS0FBS3BDLFVBQUwsS0FBb0IsTUFBeEIsRUFBZ0M7QUFDOUI7QUFDRDs7QUFFRCxVQUFJLEtBQUtXLFVBQVQsRUFBcUI7QUFDbkIsYUFBS0MsYUFBTCxDQUFtQjhCLElBQW5CLENBQXdCTixJQUF4QjtBQUNBO0FBQ0Q7O0FBRUQ7QUFDQSxXQUFLMUIsV0FBTCxDQUFpQmlDLFVBQWpCLENBQTRCUCxJQUE1Qjs7QUFFQTtBQUNBLFdBQUsxQixXQUFMLENBQWlCa0MsVUFBakIsR0FBOEI1QixJQUE5QixDQUFtQztBQUFBLGVBQU0sT0FBS0UsS0FBTCxDQUFXLE9BQVgsQ0FBTjtBQUFBLE9BQW5DLEVBQThELFVBQUNDLENBQUQ7QUFBQSxlQUFPLE9BQUtxQixhQUFMLENBQW1CckIsQ0FBbkIsQ0FBUDtBQUFBLE9BQTlEO0FBQ0Q7OztzQ0FFa0I7QUFBQTs7QUFDakIsVUFBSSxLQUFLckIsR0FBTCxJQUFZLEtBQUthLFVBQXJCLEVBQWlDOztBQUVqQyxXQUFLQSxVQUFMLEdBQWtCLElBQWxCO0FBQ0EsVUFBSTtBQUNGO0FBQ0E7QUFDQTtBQUNBLGFBQUtGLFdBQUwsQ0FBaUJvQyxZQUFqQjtBQUNELE9BTEQsQ0FLRSxPQUFPSixDQUFQLEVBQVUsQ0FBRzs7QUFFZjtBQUNBLFdBQUs1QixnQkFBTCxHQUF3QmxCLFFBQVFDLFVBQVIsQ0FBbUJRLE9BQW5CLENBQTJCVSxxQkFBM0IsQ0FBaURnQyxLQUF6RTs7QUFFQSxXQUFLM0MsT0FBTCxDQUFhNEMsaUJBQWIsQ0FBK0IsS0FBS2xDLGdCQUFwQyxFQUFzRCxLQUFLckIsSUFBM0QsRUFBaUV3QixJQUFqRSxDQUNFLFlBQU07QUFDSixlQUFLTCxVQUFMLEdBQWtCLEtBQWxCO0FBQ0EsZUFBS2IsR0FBTCxHQUFXLElBQVgsQ0FGSSxDQUVZOztBQUVoQixlQUFLVyxXQUFMLEdBQW1CLElBQUlkLFFBQVF5QixPQUFSLENBQWdCQyxPQUFoQixDQUF3QkMsVUFBNUIsQ0FBdUMsT0FBS25CLE9BQUwsQ0FBYW9CLFdBQXBELENBQW5CO0FBQ0EsZUFBS2QsV0FBTCxDQUFpQmUsa0JBQWpCLEdBQXNDN0IsUUFBUXlCLE9BQVIsQ0FBZ0JDLE9BQWhCLENBQXdCSSxrQkFBeEIsQ0FBMkNDLE9BQWpGO0FBQ0EsZUFBS0csS0FBTDs7QUFFQTtBQUNBLGVBQU8sT0FBS2pCLGFBQUwsQ0FBbUJvQyxNQUExQixFQUFrQztBQUNoQyxjQUFNWixPQUFPLE9BQUt4QixhQUFMLENBQW1CcUMsS0FBbkIsRUFBYjtBQUNBLGlCQUFLQyxJQUFMLENBQVVkLElBQVY7QUFDRDtBQUNGLE9BZEgsRUFlRSxVQUFDakIsQ0FBRCxFQUFPO0FBQ0wsZUFBS1IsVUFBTCxHQUFrQixLQUFsQjtBQUNBLGVBQUs2QixhQUFMLENBQW1CckIsQ0FBbkI7QUFDRCxPQWxCSDtBQW9CRDs7OzBCQUVNZ0MsSSxFQUFNZixJLEVBQU07QUFDakIsVUFBTWdCLFNBQVMsSUFBZjtBQUNBLGNBQVFELElBQVI7QUFDRSxhQUFLLE1BQUw7QUFDRSxlQUFLbkQsVUFBTCxHQUFrQixNQUFsQjtBQUNBLGVBQUtxRCxNQUFMLElBQWUsS0FBS0EsTUFBTCxDQUFZLEVBQUVELGNBQUYsRUFBVUQsVUFBVixFQUFnQmYsVUFBaEIsRUFBWixDQUFmO0FBQ0E7QUFDRixhQUFLLE9BQUw7QUFDRSxlQUFLa0IsT0FBTCxJQUFnQixLQUFLQSxPQUFMLENBQWEsRUFBRUYsY0FBRixFQUFVRCxVQUFWLEVBQWdCZixVQUFoQixFQUFiLENBQWhCO0FBQ0E7QUFDRixhQUFLLE1BQUw7QUFDRSxlQUFLbUIsTUFBTCxJQUFlLEtBQUtBLE1BQUwsQ0FBWSxFQUFFSCxjQUFGLEVBQVVELFVBQVYsRUFBZ0JmLFVBQWhCLEVBQVosQ0FBZjtBQUNBO0FBQ0YsYUFBSyxPQUFMO0FBQ0UsZUFBS29CLE9BQUwsSUFBZ0IsS0FBS0EsT0FBTCxDQUFhLEVBQUVKLGNBQUYsRUFBVUQsVUFBVixFQUFnQmYsVUFBaEIsRUFBYixDQUFoQjtBQUNBO0FBQ0YsYUFBSyxPQUFMO0FBQ0UsZUFBS3BDLFVBQUwsR0FBa0IsUUFBbEI7QUFDQSxlQUFLeUQsT0FBTCxJQUFnQixLQUFLQSxPQUFMLENBQWEsRUFBRUwsY0FBRixFQUFVRCxVQUFWLEVBQWdCZixVQUFoQixFQUFiLENBQWhCO0FBQ0E7QUFqQko7QUFtQkQ7Ozs7OztrQkEvTGtCN0MsUyIsImZpbGUiOiJ3aW5kb3dzLXNvY2tldC5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHByb3BPciB9IGZyb20gJ3JhbWRhJ1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBUQ1BTb2NrZXQge1xuICBzdGF0aWMgb3BlbiAoaG9zdCwgcG9ydCwgb3B0aW9ucyA9IHt9KSB7XG4gICAgcmV0dXJuIG5ldyBUQ1BTb2NrZXQoeyBob3N0LCBwb3J0LCBvcHRpb25zIH0pXG4gIH1cblxuICBjb25zdHJ1Y3RvciAoeyBob3N0LCBwb3J0LCBvcHRpb25zIH0pIHtcbiAgICB0aGlzLmhvc3QgPSBuZXcgV2luZG93cy5OZXR3b3JraW5nLkhvc3ROYW1lKGhvc3QpIC8vIE5CISBIb3N0TmFtZSBjb25zdHJ1Y3RvciB3aWxsIHRocm93IG9uIGludmFsaWQgaW5wdXRcbiAgICB0aGlzLnBvcnQgPSBwb3J0XG4gICAgdGhpcy5zc2wgPSBwcm9wT3IoZmFsc2UsICd1c2VTZWN1cmVUcmFuc3BvcnQnKShvcHRpb25zKVxuICAgIHRoaXMuYnVmZmVyZWRBbW91bnQgPSAwXG4gICAgdGhpcy5yZWFkeVN0YXRlID0gJ2Nvbm5lY3RpbmcnXG4gICAgdGhpcy5iaW5hcnlUeXBlID0gcHJvcE9yKCdhcnJheWJ1ZmZlcicsICdiaW5hcnlUeXBlJykob3B0aW9ucylcblxuICAgIGlmICh0aGlzLmJpbmFyeVR5cGUgIT09ICdhcnJheWJ1ZmZlcicpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignT25seSBhcnJheWJ1ZmZlcnMgYXJlIHN1cHBvcnRlZCEnKVxuICAgIH1cblxuICAgIHRoaXMuX3NvY2tldCA9IG5ldyBXaW5kb3dzLk5ldHdvcmtpbmcuU29ja2V0cy5TdHJlYW1Tb2NrZXQoKVxuXG4gICAgdGhpcy5fc29ja2V0LmNvbnRyb2wua2VlcEFsaXZlID0gdHJ1ZVxuICAgIHRoaXMuX3NvY2tldC5jb250cm9sLm5vRGVsYXkgPSB0cnVlXG5cbiAgICB0aGlzLl9kYXRhUmVhZGVyID0gbnVsbFxuICAgIHRoaXMuX2RhdGFXcml0ZXIgPSBudWxsXG5cbiAgICAvLyBzZXQgdG8gdHJ1ZSBpZiB1cGdyYWRpbmcgd2l0aCBTVEFSVFRMU1xuICAgIHRoaXMuX3VwZ3JhZGluZyA9IGZhbHNlXG5cbiAgICAvLyBjYWNoZSBhbGwgY2xpZW50LnNlbmQgY2FsbHMgdG8gdGhpcyBhcnJheSBpZiBjdXJyZW50bHkgdXBncmFkaW5nXG4gICAgdGhpcy5fdXBncmFkZUNhY2hlID0gW11cblxuICAgIC8vIGluaXRpYWwgc29ja2V0IHR5cGUuIGRlZmF1bHQgaXMgJ3BsYWluU29ja2V0JyAobm8gZW5jcnlwdGlvbiBhcHBsaWVkKVxuICAgIC8vICd0bHMxMicgc3VwcG9ydHMgdGhlIFRMUyAxLjIsIFRMUyAxLjEgYW5kIFRMUyAxLjAgcHJvdG9jb2xzIGJ1dCBubyBTU0xcbiAgICB0aGlzLl9wcm90ZWN0aW9uTGV2ZWwgPSBXaW5kb3dzLk5ldHdvcmtpbmcuU29ja2V0cy5Tb2NrZXRQcm90ZWN0aW9uTGV2ZWxbdGhpcy5zc2wgPyAndGxzMTInIDogJ3BsYWluU29ja2V0J11cblxuICAgIC8vIEluaXRpYXRlIGNvbm5lY3Rpb24gdG8gZGVzdGluYXRpb25cbiAgICB0aGlzLl9zb2NrZXRcbiAgICAgIC5jb25uZWN0QXN5bmModGhpcy5ob3N0LCB0aGlzLnBvcnQsIHRoaXMuX3Byb3RlY3Rpb25MZXZlbClcbiAgICAgIC5kb25lKCgpID0+IHtcbiAgICAgICAgdGhpcy5fc2V0U3RyZWFtSGFuZGxlcnMoKVxuICAgICAgICB0aGlzLl9lbWl0KCdvcGVuJylcbiAgICAgIH0sIGUgPT4gdGhpcy5fZW1pdCgnZXJyb3InLCBlKSlcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWF0ZSBSZWFkZXIgYW5kIFdyaXRlciBpbnRlcmZhY2VzIGZvciB0aGUgc29ja2V0XG4gICAqL1xuICBfc2V0U3RyZWFtSGFuZGxlcnMgKCkge1xuICAgIHRoaXMuX2RhdGFSZWFkZXIgPSBuZXcgV2luZG93cy5TdG9yYWdlLlN0cmVhbXMuRGF0YVJlYWRlcih0aGlzLl9zb2NrZXQuaW5wdXRTdHJlYW0pXG4gICAgdGhpcy5fZGF0YVJlYWRlci5pbnB1dFN0cmVhbU9wdGlvbnMgPSBXaW5kb3dzLlN0b3JhZ2UuU3RyZWFtcy5JbnB1dFN0cmVhbU9wdGlvbnMucGFydGlhbFxuXG4gICAgLy8gc2V0dXAgd3JpdGVyXG4gICAgdGhpcy5fZGF0YVdyaXRlciA9IG5ldyBXaW5kb3dzLlN0b3JhZ2UuU3RyZWFtcy5EYXRhV3JpdGVyKHRoaXMuX3NvY2tldC5vdXRwdXRTdHJlYW0pXG5cbiAgICAvLyBzdGFydCBieXRlIHJlYWRlciBsb29wXG4gICAgdGhpcy5fcmVhZCgpXG4gIH1cblxuICAvKipcbiAgICogRW1pdCBhbiBlcnJvciBhbmQgY2xvc2Ugc29ja2V0XG4gICAqXG4gICAqIEBwYXJhbSB7RXJyb3J9IGVycm9yIEVycm9yIG9iamVjdFxuICAgKi9cbiAgX2Vycm9ySGFuZGxlciAoZXJyb3IpIHtcbiAgICAvLyB3ZSBpZ25vcmUgZXJyb3JzIGFmdGVyIGNsb3NlIGhhcyBiZWVuIGNhbGxlZCwgc2luY2UgYWxsIGFib3J0ZWQgb3BlcmF0aW9uc1xuICAgIC8vIHdpbGwgZW1pdCB0aGVpciBlcnJvciBoYW5kbGVyc1xuICAgIC8vIHRoaXMgd2lsbCBhbHNvIGFwcGx5IHRvIHN0YXJ0dGxzIGFzIGEgcmVhZCBjYWxsIGlzIGFib3J0ZWQgYmVmb3JlIHVwZ3JhZGluZyB0aGUgc29ja2V0XG4gICAgaWYgKHRoaXMuX3VwZ3JhZGluZyB8fCAodGhpcy5yZWFkeVN0YXRlICE9PSAnY2xvc2luZycgJiYgdGhpcy5yZWFkeVN0YXRlICE9PSAnY2xvc2VkJykpIHtcbiAgICAgIHRoaXMuX2VtaXQoJ2Vycm9yJywgZXJyb3IpXG4gICAgICB0aGlzLmNsb3NlKClcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmVhZCBhdmFpbGFibGUgYnl0ZXMgZnJvbSB0aGUgc29ja2V0LiBUaGlzIG1ldGhvZCBpcyByZWN1cnNpdmUgIG9uY2UgaXQgZW5kcywgaXQgcmVzdGFydHMgaXR0aGlzXG4gICAqL1xuICBfcmVhZCAoKSB7XG4gICAgaWYgKHRoaXMuX3VwZ3JhZGluZyB8fCAodGhpcy5yZWFkeVN0YXRlICE9PSAnb3BlbicgJiYgdGhpcy5yZWFkeVN0YXRlICE9PSAnY29ubmVjdGluZycpKSB7XG4gICAgICByZXR1cm4gLy8gZG8gbm90aGluZyBpZiBzb2NrZXQgbm90IG9wZW5cbiAgICB9XG5cbiAgICAvLyBSZWFkIHVwIHRvIDQwOTYgYnl0ZXMgZnJvbSB0aGUgc29ja2V0LiBUaGlzIGlzIG5vdCBhIGZpeGVkIG51bWJlciAodGhlIG1vZGUgd2FzIHNldFxuICAgIC8vIHdpdGggaW5wdXRTdHJlYW1PcHRpb25zLnBhcnRpYWwgcHJvcGVydHkpLCBzbyBpdCBtaWdodCByZXR1cm4gd2l0aCBhIHNtYWxsZXJcbiAgICAvLyBhbW91bnQgb2YgYnl0ZXMuXG4gICAgdGhpcy5fZGF0YVJlYWRlci5sb2FkQXN5bmMoNDA5NikuZG9uZShhdmFpbGFibGVCeXRlQ291bnQgPT4ge1xuICAgICAgaWYgKCFhdmFpbGFibGVCeXRlQ291bnQpIHtcbiAgICAgICAgLy8gbm8gYnl0ZXMgYXZhaWxhYmxlIGZvciByZWFkaW5nLCByZXN0YXJ0IHRoZSByZWFkaW5nIHByb2Nlc3NcbiAgICAgICAgcmV0dXJuIHNldEltbWVkaWF0ZSh0aGlzLl9yZWFkLmJpbmQodGhpcykpXG4gICAgICB9XG5cbiAgICAgIC8vIHdlIG5lZWQgYW4gVWludDhBcnJheSB0aGF0IGdldHMgZmlsbGVkIHdpdGggdGhlIGJ5dGVzIGZyb20gdGhlIGJ1ZmZlclxuICAgICAgdmFyIGRhdGEgPSBuZXcgVWludDhBcnJheShhdmFpbGFibGVCeXRlQ291bnQpXG4gICAgICB0aGlzLl9kYXRhUmVhZGVyLnJlYWRCeXRlcyhkYXRhKSAvLyBkYXRhIGFyZ3VtZW50IGdldHMgZmlsbGVkIHdpdGggdGhlIGJ5dGVzXG5cbiAgICAgIHRoaXMuX2VtaXQoJ2RhdGEnLCBkYXRhLmJ1ZmZlcilcblxuICAgICAgLy8gcmVzdGFydCByZWFkaW5nIHByb2Nlc3NcbiAgICAgIHJldHVybiBzZXRJbW1lZGlhdGUodGhpcy5fcmVhZC5iaW5kKHRoaXMpKVxuICAgIH0sIGUgPT4gdGhpcy5fZXJyb3JIYW5kbGVyKGUpKVxuICB9XG5cbiAgLy9cbiAgLy8gQVBJXG4gIC8vXG5cbiAgY2xvc2UgKCkge1xuICAgIHRoaXMucmVhZHlTdGF0ZSA9ICdjbG9zaW5nJ1xuXG4gICAgdHJ5IHtcbiAgICAgIHRoaXMuX3NvY2tldC5jbG9zZSgpXG4gICAgfSBjYXRjaCAoRSkge1xuICAgICAgdGhpcy5fZW1pdCgnZXJyb3InLCBFKVxuICAgIH1cblxuICAgIHNldEltbWVkaWF0ZSh0aGlzLl9lbWl0LmJpbmQodGhpcywgJ2Nsb3NlJykpXG4gIH1cblxuICBzZW5kIChkYXRhKSB7XG4gICAgaWYgKHRoaXMucmVhZHlTdGF0ZSAhPT0gJ29wZW4nKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBpZiAodGhpcy5fdXBncmFkaW5nKSB7XG4gICAgICB0aGlzLl91cGdyYWRlQ2FjaGUucHVzaChkYXRhKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gV3JpdGUgYnl0ZXMgdG8gYnVmZmVyXG4gICAgdGhpcy5fZGF0YVdyaXRlci53cml0ZUJ5dGVzKGRhdGEpXG5cbiAgICAvLyBFbWl0IGJ1ZmZlciBjb250ZW50c1xuICAgIHRoaXMuX2RhdGFXcml0ZXIuc3RvcmVBc3luYygpLmRvbmUoKCkgPT4gdGhpcy5fZW1pdCgnZHJhaW4nKSwgKGUpID0+IHRoaXMuX2Vycm9ySGFuZGxlcihlKSlcbiAgfVxuXG4gIHVwZ3JhZGVUb1NlY3VyZSAoKSB7XG4gICAgaWYgKHRoaXMuc3NsIHx8IHRoaXMuX3VwZ3JhZGluZykgcmV0dXJuXG5cbiAgICB0aGlzLl91cGdyYWRpbmcgPSB0cnVlXG4gICAgdHJ5IHtcbiAgICAgIC8vIHJlbGVhc2UgY3VycmVudCBpbnB1dCBzdHJlYW0uIHRoaXMgaXMgcmVxdWlyZWQgdG8gYWxsb3cgc29ja2V0IHVwZ3JhZGVcbiAgICAgIC8vIHdyaXRlIHN0cmVhbSBpcyBub3QgcmVsZWFzZWQgYXMgYWxsIHNlbmQgY2FsbHMgYXJlIGNhY2hlZCBmcm9tIHRoaXMgcG9pbnQgb253YXJkc1xuICAgICAgLy8gYW5kIG5vdCBwYXNzZWQgdG8gc29ja2V0IHVudGlsIHRoZSBzb2NrZXQgaXMgdXBncmFkZWRcbiAgICAgIHRoaXMuX2RhdGFSZWFkZXIuZGV0YWNoU3RyZWFtKClcbiAgICB9IGNhdGNoIChFKSB7IH1cblxuICAgIC8vIHVwZGF0ZSBwcm90ZWN0aW9uIGxldmVsXG4gICAgdGhpcy5fcHJvdGVjdGlvbkxldmVsID0gV2luZG93cy5OZXR3b3JraW5nLlNvY2tldHMuU29ja2V0UHJvdGVjdGlvbkxldmVsLnRsczEyXG5cbiAgICB0aGlzLl9zb2NrZXQudXBncmFkZVRvU3NsQXN5bmModGhpcy5fcHJvdGVjdGlvbkxldmVsLCB0aGlzLmhvc3QpLmRvbmUoXG4gICAgICAoKSA9PiB7XG4gICAgICAgIHRoaXMuX3VwZ3JhZGluZyA9IGZhbHNlXG4gICAgICAgIHRoaXMuc3NsID0gdHJ1ZSAvLyBzZWN1cmVkIGNvbm5lY3Rpb24gZnJvbSBub3cgb25cblxuICAgICAgICB0aGlzLl9kYXRhUmVhZGVyID0gbmV3IFdpbmRvd3MuU3RvcmFnZS5TdHJlYW1zLkRhdGFSZWFkZXIodGhpcy5fc29ja2V0LmlucHV0U3RyZWFtKVxuICAgICAgICB0aGlzLl9kYXRhUmVhZGVyLmlucHV0U3RyZWFtT3B0aW9ucyA9IFdpbmRvd3MuU3RvcmFnZS5TdHJlYW1zLklucHV0U3RyZWFtT3B0aW9ucy5wYXJ0aWFsXG4gICAgICAgIHRoaXMuX3JlYWQoKVxuXG4gICAgICAgIC8vIGVtaXQgYWxsIGNhY2hlZCByZXF1ZXN0c1xuICAgICAgICB3aGlsZSAodGhpcy5fdXBncmFkZUNhY2hlLmxlbmd0aCkge1xuICAgICAgICAgIGNvbnN0IGRhdGEgPSB0aGlzLl91cGdyYWRlQ2FjaGUuc2hpZnQoKVxuICAgICAgICAgIHRoaXMuc2VuZChkYXRhKVxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgKGUpID0+IHtcbiAgICAgICAgdGhpcy5fdXBncmFkaW5nID0gZmFsc2VcbiAgICAgICAgdGhpcy5fZXJyb3JIYW5kbGVyKGUpXG4gICAgICB9XG4gICAgKVxuICB9XG5cbiAgX2VtaXQgKHR5cGUsIGRhdGEpIHtcbiAgICBjb25zdCB0YXJnZXQgPSB0aGlzXG4gICAgc3dpdGNoICh0eXBlKSB7XG4gICAgICBjYXNlICdvcGVuJzpcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ29wZW4nXG4gICAgICAgIHRoaXMub25vcGVuICYmIHRoaXMub25vcGVuKHsgdGFyZ2V0LCB0eXBlLCBkYXRhIH0pXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgIHRoaXMub25lcnJvciAmJiB0aGlzLm9uZXJyb3IoeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2RhdGEnOlxuICAgICAgICB0aGlzLm9uZGF0YSAmJiB0aGlzLm9uZGF0YSh7IHRhcmdldCwgdHlwZSwgZGF0YSB9KVxuICAgICAgICBicmVha1xuICAgICAgY2FzZSAnZHJhaW4nOlxuICAgICAgICB0aGlzLm9uZHJhaW4gJiYgdGhpcy5vbmRyYWluKHsgdGFyZ2V0LCB0eXBlLCBkYXRhIH0pXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdjbG9zZSc6XG4gICAgICAgIHRoaXMucmVhZHlTdGF0ZSA9ICdjbG9zZWQnXG4gICAgICAgIHRoaXMub25jbG9zZSAmJiB0aGlzLm9uY2xvc2UoeyB0YXJnZXQsIHR5cGUsIGRhdGEgfSlcbiAgICAgICAgYnJlYWtcbiAgICB9XG4gIH1cbn1cbiJdfQ== --------------------------------------------------------------------------------