diff options
author | Jason <jason.daurus@gmail.com> | 2016-06-17 21:54:31 +0800 |
---|---|---|
committer | Jason <jason.daurus@gmail.com> | 2016-06-17 21:54:31 +0800 |
commit | e4addd60f1ffbb445a8c05f3fb2b6c6e61bd7b93 (patch) | |
tree | cb5e3be10e8bb813c1ade3aa88204460317d2548 | |
parent | 16a28eca258e07d45c7e2a8ee95368d4eb077d4d (diff) | |
parent | 9c6199db9be34fad18eaedb86463333671ae190a (diff) | |
download | mitmproxy-e4addd60f1ffbb445a8c05f3fb2b6c6e61bd7b93.tar.gz mitmproxy-e4addd60f1ffbb445a8c05f3fb2b6c6e61bd7b93.tar.bz2 mitmproxy-e4addd60f1ffbb445a8c05f3fb2b6c6e61bd7b93.zip |
Merge branch 'master' into websocket
Conflicts:
mitmproxy/web/static/app.css
mitmproxy/web/static/app.js
web/src/js/components/ProxyApp.jsx
63 files changed, 2127 insertions, 2103 deletions
diff --git a/codecov.yml b/.codecov.yml index db247200..db247200 100644 --- a/codecov.yml +++ b/.codecov.yml @@ -94,6 +94,21 @@ requirements installed, and you can simply run the test suite: Please ensure that all patches are accompanied by matching changes in the test suite. The project tries to maintain 100% test coverage. +You can also use `tox` to run a full suite of tests in Python 2.7 and 3.5, +including a quick test to check documentation and code linting. + +The following tox environments are relevant for local testing: + +.. code-block:: text + + tox -e py27 # runs all tests with Python 2.7 + tox -e py35 # runs all tests with Python 3.5 (partial support only) + tox -e docs # runs a does-it-compile check on the documentation + tox -e lint # runs the linter for coding style checks + +We are in the middle of transitioning to Python 3, so please make sure all tests +pass in Python 2.7 and 3.5. Running `tox` ensure all necessary tests are executed. + Documentation ------------- @@ -120,6 +135,13 @@ contribute and collaborate. Please stick to the guidelines in `PEP8`_ and the `Google Style Guide`_ unless there's a very good reason not to. +This is automatically enforced on every PR. If we detect a linting error, the +PR checks will fail and block merging. We are using this command to check for style compliance: + +.. code-block:: text + + flake8 --jobs 8 --count mitmproxy netlib pathod examples test + .. |mitmproxy_site| image:: https://shields.mitmproxy.org/api/https%3A%2F%2F-mitmproxy.org-blue.svg :target: https://mitmproxy.org/ diff --git a/mitmproxy/web/static/app.css b/mitmproxy/web/static/app.css index bd4b57be..c2dd139f 100644 --- a/mitmproxy/web/static/app.css +++ b/mitmproxy/web/static/app.css @@ -1,2 +1,2 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}.resource-icon{width:32px;height:32px}.resource-icon-css{background-image:url(images/chrome-devtools/resourceCSSIcon.png)}.resource-icon-document{background-image:url(images/chrome-devtools/resourceDocumentIcon.png)}.resource-icon-js{background-image:url(images/chrome-devtools/resourceJSIcon.png)}.resource-icon-plain{background-image:url(images/chrome-devtools/resourcePlainIcon.png)}.resource-icon-executable{background-image:url(images/resourceExecutableIcon.png)}.resource-icon-flash{background-image:url(images/resourceFlashIcon.png)}.resource-icon-image{background-image:url(images/resourceImageIcon.png)}.resource-icon-java{background-image:url(images/resourceJavaIcon.png)}.resource-icon-not-modified{background-image:url(images/resourceNotModifiedIcon.png)}.resource-icon-redirect{background-image:url(images/resourceRedirectIcon.png)}#container,#mitmproxy,body,html{height:100%;margin:0;overflow:hidden}#container{display:flex;flex-direction:column;outline:0}#container>.eventlog,#container>footer,#container>header,.eventlog{flex:0 0 auto}.main-view{flex:1 1 auto;height:0;display:flex;flex-direction:row}.main-view.vertical{flex-direction:column}.main-view .flow-detail,.main-view .flow-table{flex:1 1 auto}.splitter{flex:0 0 1px;background-color:#aaa;position:relative}.splitter>div{position:absolute}.splitter.splitter-x{cursor:col-resize}.splitter.splitter-x>div{margin-left:-1px;width:4px;height:100%}.splitter.splitter-y{cursor:row-resize}.eventlog .label,.flow-table tr,.prompt-content .option{cursor:pointer}.splitter.splitter-y>div{margin-top:-1px;height:4px;width:100%}.nav-tabs{border-bottom:solid #a6a6a6 1px}.nav-tabs a{display:inline-block;border:1px solid transparent;text-decoration:none}.nav-tabs a.active{background-color:#fff;border-color:#a6a6a6 #a6a6a6 #fff}.nav-tabs a.special{color:#fff;background-color:#396cad;border-bottom-color:#396cad}.nav-tabs a.special:hover{background-color:#5386c6}.nav-tabs-lg a{padding:3px 14px;margin:0 2px -1px}.nav-tabs-sm a{padding:0 7px;margin:2px 2px -1px}.nav-tabs-sm a.nav-action{float:right;padding:0;margin:1px 0 0}header{padding-top:.5em;background-color:#fff}header .menu{padding:10px;border-bottom:solid #a6a6a6 1px}.menu-row{margin-left:-2px;margin-right:-3px}.filter-input{position:relative;min-height:1px;padding-left:2.5px;padding-right:2.5px;margin-bottom:5px}@media (min-width:768px){.filter-input{float:left;width:25%}}.filter-input .popover{top:27px;display:block;max-width:none;opacity:.9}.filter-input .popover .popover-content{max-height:500px;overflow-y:auto}.flow-table{width:100%;overflow-y:scroll;overflow-x:hidden}.flow-table table{width:100%;table-layout:fixed}.flow-table thead{background-color:#F2F2F2;line-height:23px}.flow-table th{font-weight:400;box-shadow:0 1px 0 #a6a6a6;position:relative!important;padding-left:1px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.prompt-content,footer{box-shadow:0 -1px 3px #d3d3d3}.flow-table th.sort-asc,.flow-table th.sort-desc{background-color:#fafafa}.flow-table th.sort-asc:after,.flow-table th.sort-desc:after{font:normal normal normal 14px/1 FontAwesome;position:absolute;right:3px;top:3px;padding:2px;background-color:rgba(250,250,250,.8)}.flow-detail .first-line,.flow-detail table{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;word-break:break-all}.prompt-content,.prompt-dialog{position:fixed;bottom:0;left:0;right:0}.flow-table th.sort-asc:after{content:"\f0de"}.flow-table th.sort-desc:after{content:"\f0dd"}.flow-table tr:nth-child(even){background-color:rgba(0,0,0,.05)}.flow-table tr.selected{background-color:rgba(193,215,235,.5)!important}.flow-table tr.highlighted{background-color:rgba(255,204,0,.4)}.flow-table tr.highlighted:nth-child(even){background-color:rgba(255,204,0,.5)}.flow-table td{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.flow-table tr.intercepted.has-response .col-size,.flow-table tr.intercepted.has-response .col-status,.flow-table tr.intercepted.has-response .col-time,.flow-table tr.intercepted:not(.has-response) .col-method,.flow-table tr.intercepted:not(.has-response) .col-path{color:#ff8000}.flow-table .fa{line-height:inherit}.flow-table .fa.pull-right{margin-left:0}.flow-table .col-tls{width:10px}.flow-table .col-tls-https{background-color:rgba(0,185,0,.5)}.flow-table .col-icon{width:32px}.flow-table .col-path .fa-repeat{color:green}.flow-table .col-path .fa-pause{color:#ff8000}.flow-table .col-method{width:60px}.flow-table .col-status{width:50px}.flow-table .col-size{width:70px}.flow-table .col-time{width:50px}.flow-table td.col-size,.flow-table td.col-time{text-align:right}.flow-detail{width:100%;overflow-x:auto;overflow-y:scroll}.flow-detail nav{background-color:#F2F2F2}.flow-detail section{padding:5px 12px}.flow-detail .first-line{background-color:#428bca;color:#fff;margin:0 -8px;padding:4px 8px;border-radius:5px;max-height:100px;overflow-y:auto}.flow-detail .request-line{margin-bottom:2px}.flow-detail hr{margin:0 0 5px}.inline-input{margin:0 -5px;padding:0 5px}.inline-input[contenteditable]{background-color:rgba(255,255,255,.2)}.inline-input[contenteditable].has-warning{color:#ffb8b8}.view-options{margin-top:10px}.flow-detail table{width:100%;table-layout:fixed}.flow-detail table tr:not(:first-child){border-top:1px solid #f7f7f7}.flow-detail table td{vertical-align:top}.connection-table td:first-child{width:50%;padding-right:1em}.header-table td{line-height:1.3em}.header-table .header-name{width:33%;padding-right:1em}.connection-table td,.timing-table td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.flowview-image{text-align:center}.flowview-image img{max-width:100%;max-height:100%}.prompt-dialog{top:0;z-index:100;background-color:rgba(0,0,0,.1)}.prompt-content{height:25px;padding:2px 5px;background-color:#fff}.prompt-content .option:not(:last-child)::after{content:", "}.eventlog{height:200px;display:flex;flex-direction:column}.eventlog>div{background-color:#F2F2F2;padding:0 5px;flex:0 0 auto}.eventlog>pre{flex:1 1 auto;margin:0;border-radius:0;overflow-x:auto;overflow-y:scroll;background-color:#fcfcfc}.eventlog .fa-close{cursor:pointer;float:right;color:grey;padding:3px 0 3px 10px}.eventlog .fa-close:hover{color:#000}.eventlog .btn-toggle{margin-top:-2px;margin-left:3px;padding:2px;font-size:10px;line-height:10px;border-radius:2px}.eventlog .label{vertical-align:middle;display:inline-block;margin-top:-2px;margin-left:3px}footer{padding:0 10px 3px}footer .label{margin-right:3px} +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}.resource-icon{width:32px;height:32px}.resource-icon-css{background-image:url(images/chrome-devtools/resourceCSSIcon.png)}.resource-icon-document{background-image:url(images/chrome-devtools/resourceDocumentIcon.png)}.resource-icon-js{background-image:url(images/chrome-devtools/resourceJSIcon.png)}.resource-icon-plain{background-image:url(images/chrome-devtools/resourcePlainIcon.png)}.resource-icon-executable{background-image:url(images/resourceExecutableIcon.png)}.resource-icon-flash{background-image:url(images/resourceFlashIcon.png)}.resource-icon-image{background-image:url(images/resourceImageIcon.png)}.resource-icon-java{background-image:url(images/resourceJavaIcon.png)}.resource-icon-not-modified{background-image:url(images/resourceNotModifiedIcon.png)}.resource-icon-redirect{background-image:url(images/resourceRedirectIcon.png)}#container,#mitmproxy,body,html{height:100%;margin:0;overflow:hidden}#container{display:flex;flex-direction:column;outline:0}#container>.eventlog,#container>footer,#container>header,.eventlog{flex:0 0 auto}.main-view{flex:1 1 auto;height:0;display:flex;flex-direction:row}.main-view.vertical{flex-direction:column}.main-view .flow-detail,.main-view .flow-table{flex:1 1 auto}.splitter{flex:0 0 1px;background-color:#aaa;position:relative}.splitter>div{position:absolute}.splitter.splitter-x{cursor:col-resize}.splitter.splitter-x>div{margin-left:-1px;width:4px;height:100%}.splitter.splitter-y{cursor:row-resize}.splitter.splitter-y>div{margin-top:-1px;height:4px;width:100%}.nav-tabs{border-bottom:solid #a6a6a6 1px}.nav-tabs a{display:inline-block;border:1px solid transparent;text-decoration:none}.nav-tabs a.active{background-color:#fff;border-color:#a6a6a6 #a6a6a6 #fff}.nav-tabs a.special{color:#fff;background-color:#396cad;border-bottom-color:#396cad}.nav-tabs a.special:hover{background-color:#5386c6}.nav-tabs-lg a{padding:3px 14px;margin:0 2px -1px}.nav-tabs-sm a{padding:0 7px;margin:2px 2px -1px}.nav-tabs-sm a.nav-action{float:right;padding:0;margin:1px 0 0}header{padding-top:.5em;background-color:#fff}header .menu{padding:10px;border-bottom:solid #a6a6a6 1px}.menu-row{margin-left:-2px;margin-right:-3px}.filter-input{position:relative;min-height:1px;padding-left:2.5px;padding-right:2.5px;margin-bottom:5px}@media (min-width:768px){.filter-input{float:left;width:25%}}.filter-input .popover{top:27px;display:block;max-width:none;opacity:.9}.filter-input .popover .popover-content{max-height:500px;overflow-y:auto}.flow-table{width:100%;overflow-y:scroll;overflow-x:hidden}.flow-table table{width:100%;table-layout:fixed}.flow-table thead{background-color:#F2F2F2;line-height:23px}.flow-table th{font-weight:400;box-shadow:0 1px 0 #a6a6a6;position:relative!important;padding-left:1px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.prompt-content,footer{box-shadow:0 -1px 3px #d3d3d3}.flow-table th.sort-asc,.flow-table th.sort-desc{background-color:#fafafa}.flow-table th.sort-asc:after,.flow-table th.sort-desc:after{font:normal normal normal 14px/1 FontAwesome;position:absolute;right:3px;top:3px;padding:2px;background-color:rgba(250,250,250,.8)}.flow-detail .first-line,.flow-detail table{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;word-break:break-all}.prompt-content,.prompt-dialog{position:fixed;bottom:0;left:0;right:0}.flow-table th.sort-asc:after{content:"\f0de"}.flow-table th.sort-desc:after{content:"\f0dd"}.flow-table tr{cursor:pointer}.flow-table tr:nth-child(even){background-color:rgba(0,0,0,.05)}.flow-table tr.selected{background-color:rgba(193,215,235,.5)!important}.flow-table tr.highlighted{background-color:rgba(255,204,0,.4)}.flow-table tr.highlighted:nth-child(even){background-color:rgba(255,204,0,.5)}.flow-table td{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.flow-table tr.intercepted.has-response .col-size,.flow-table tr.intercepted.has-response .col-status,.flow-table tr.intercepted.has-response .col-time,.flow-table tr.intercepted:not(.has-response) .col-method,.flow-table tr.intercepted:not(.has-response) .col-path{color:#ff8000}.flow-table .fa{line-height:inherit}.flow-table .fa.pull-right{margin-left:0}.flow-table .col-tls{width:10px}.flow-table .col-tls-https{background-color:rgba(0,185,0,.5)}.flow-table .col-icon{width:32px}.flow-table .col-path .fa-repeat{color:green}.flow-table .col-path .fa-pause{color:#ff8000}.flow-table .col-method{width:60px}.flow-table .col-status{width:50px}.flow-table .col-size{width:70px}.flow-table .col-time{width:50px}.flow-table td.col-size,.flow-table td.col-time{text-align:right}.flow-detail{width:100%;overflow-x:auto;overflow-y:scroll}.flow-detail nav{background-color:#F2F2F2}.flow-detail section{padding:5px 12px}.flow-detail .first-line{background-color:#428bca;color:#fff;margin:0 -8px;padding:4px 8px;border-radius:5px;max-height:100px;overflow-y:auto}.flow-detail .request-line{margin-bottom:2px}.flow-detail hr{margin:0 0 5px}.inline-input{margin:0 -5px;padding:0 5px}.inline-input[contenteditable]{background-color:rgba(255,255,255,.2)}.inline-input[contenteditable].has-warning{color:#ffb8b8}.view-options{margin-top:10px}.flow-detail table{width:100%;table-layout:fixed}.flow-detail table tr:not(:first-child){border-top:1px solid #f7f7f7}.flow-detail table td{vertical-align:top}.connection-table td:first-child{width:50%;padding-right:1em}.header-table td{line-height:1.3em}.header-table .header-name{width:33%;padding-right:1em}.connection-table td,.timing-table td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.flowview-image{text-align:center}.flowview-image img{max-width:100%;max-height:100%}.prompt-dialog{top:0;z-index:100;background-color:rgba(0,0,0,.1)}.prompt-content{height:25px;padding:2px 5px;background-color:#fff}.prompt-content .option{cursor:pointer}.prompt-content .option:not(:last-child)::after{content:", "}.eventlog{height:200px;display:flex;flex-direction:column}.eventlog>div{background-color:#F2F2F2;padding:0 5px;flex:0 0 auto;border-top:1px solid #aaa;cursor:row-resize}.eventlog>pre{flex:1 1 auto;margin:0;border-radius:0;overflow-x:auto;overflow-y:scroll;background-color:#fcfcfc}.eventlog .fa-close{cursor:pointer;float:right;color:grey;padding:3px 0 3px 10px}.eventlog .fa-close:hover{color:#000}.eventlog .btn-toggle{margin-top:-2px;margin-left:3px;padding:2px;font-size:10px;line-height:10px;border-radius:2px}.eventlog .label{cursor:pointer;vertical-align:middle;display:inline-block;margin-top:-2px;margin-left:3px}footer{padding:0 10px 3px}footer .label{margin-right:3px} /*# sourceMappingURL=app.css.map */ diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index a6833775..22706a7f 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -4,124 +4,154 @@ function EventEmitter(){this._events=this._events||{},this._maxListeners=this._m },{}],2:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.Query=exports.FlowActions=exports.SettingsActions=exports.ConnectionActions=exports.StoreCmds=exports.ActionTypes=void 0;var _jquery=require("jquery"),_jquery2=_interopRequireDefault(_jquery),_dispatcher=require("./dispatcher.js"),_utils=require("./utils.js"),ActionTypes=exports.ActionTypes={CONNECTION_OPEN:"connection_open",CONNECTION_CLOSE:"connection_close",CONNECTION_ERROR:"connection_error",SETTINGS_STORE:"settings",EVENT_STORE:"events",FLOW_STORE:"flows"},StoreCmds=exports.StoreCmds={ADD:"add",UPDATE:"update",REMOVE:"remove",RESET:"reset"},ConnectionActions=exports.ConnectionActions={open:function(){_dispatcher.AppDispatcher.dispatchViewAction({type:ActionTypes.CONNECTION_OPEN})},close:function(){_dispatcher.AppDispatcher.dispatchViewAction({type:ActionTypes.CONNECTION_CLOSE})},error:function(){_dispatcher.AppDispatcher.dispatchViewAction({type:ActionTypes.CONNECTION_ERROR})}},SettingsActions=exports.SettingsActions={update:function(e){_jquery2["default"].ajax({type:"PUT",url:"/settings",contentType:"application/json",data:JSON.stringify(e)})}},FlowActions=exports.FlowActions={accept:function(e){_jquery2["default"].post("/flows/"+e.id+"/accept")},accept_all:function(){_jquery2["default"].post("/flows/accept")},"delete":function(e){_jquery2["default"].ajax({type:"DELETE",url:"/flows/"+e.id})},duplicate:function(e){_jquery2["default"].post("/flows/"+e.id+"/duplicate")},replay:function(e){_jquery2["default"].post("/flows/"+e.id+"/replay")},revert:function(e){_jquery2["default"].post("/flows/"+e.id+"/revert")},update:function(e,t){_jquery2["default"].ajax({type:"PUT",url:"/flows/"+e.id,contentType:"application/json",data:JSON.stringify(t)})},clear:function(){_jquery2["default"].post("/clear")},download:function(){return window.location="/flows/dump"},upload:function(e){var t=new FormData;t.append("file",e),(0,_utils.fetchApi)("/flows/dump",{method:"post",body:t})}},Query=exports.Query={SEARCH:"s",HIGHLIGHT:"h",SHOW_EVENTLOG:"e"}; -},{"./dispatcher.js":31,"./utils.js":42,"jquery":"jquery"}],3:[function(require,module,exports){ +},{"./dispatcher.js":41,"./utils.js":52,"jquery":"jquery"}],3:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_redux=require("redux"),_reactRedux=require("react-redux"),_reduxLogger=require("redux-logger"),_reduxLogger2=_interopRequireDefault(_reduxLogger),_reduxThunk=require("redux-thunk"),_reduxThunk2=_interopRequireDefault(_reduxThunk),_reactRouter=require("react-router"),_ProxyApp=require("./components/ProxyApp"),_ProxyApp2=_interopRequireDefault(_ProxyApp),_MainView=require("./components/MainView"),_MainView2=_interopRequireDefault(_MainView),_index=require("./ducks/index"),_index2=_interopRequireDefault(_index),_eventLog=require("./ducks/eventLog"),store=(0,_redux.createStore)(_index2["default"],(0,_redux.applyMiddleware)(_reduxThunk2["default"],(0,_reduxLogger2["default"])()));window.addEventListener("error",function(e){store.dispatch((0,_eventLog.addLogEntry)(e))}),document.addEventListener("DOMContentLoaded",function(){(0,_reactDom.render)(_react2["default"].createElement(_reactRedux.Provider,{store:store},_react2["default"].createElement(_reactRouter.Router,{history:_reactRouter.hashHistory},_react2["default"].createElement(_reactRouter.Redirect,{from:"/",to:"/flows"}),_react2["default"].createElement(_reactRouter.Route,{path:"/",component:_ProxyApp2["default"]},_react2["default"].createElement(_reactRouter.Route,{path:"flows",component:_MainView2["default"]}),_react2["default"].createElement(_reactRouter.Route,{path:"flows/:flowId/:detailTab",component:_MainView2["default"]})))),document.getElementById("mitmproxy"))}); -},{"./components/MainView":19,"./components/ProxyApp":20,"./ducks/eventLog":32,"./ducks/index":34,"react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router","redux":"redux","redux-logger":"redux-logger","redux-thunk":"redux-thunk"}],4:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function EventLog(e){var t=e.filters,r=e.events,n=e.onToggleFilter,o=e.onClose;return _react2["default"].createElement("div",{className:"eventlog"},_react2["default"].createElement("div",null,"Eventlog",_react2["default"].createElement("div",{className:"pull-right"},["debug","info","web"].map(function(e){return _react2["default"].createElement(_common.ToggleButton,{key:e,text:e,checked:t[e],onToggle:function(){return n(e)}})}),_react2["default"].createElement("i",{onClick:o,className:"fa fa-close"}))),_react2["default"].createElement(_EventList2["default"],{events:r}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_eventLog=require("../ducks/eventLog"),_common=require("./common"),_EventList=require("./EventLog/EventList"),_EventList2=_interopRequireDefault(_EventList);EventLog.propTypes={filters:_react.PropTypes.object.isRequired,events:_react.PropTypes.array.isRequired,onToggleFilter:_react.PropTypes.func.isRequired,onClose:_react.PropTypes.func.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{filters:e.eventLog.filter,events:e.eventLog.filteredEvents}},{onClose:_eventLog.toggleEventLogVisibility,onToggleFilter:_eventLog.toggleEventLogFilter})(EventLog); +},{"./components/MainView":29,"./components/ProxyApp":31,"./ducks/eventLog":42,"./ducks/index":44,"react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router","redux":"redux","redux-logger":"redux-logger","redux-thunk":"redux-thunk"}],4:[function(require,module,exports){ +"use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t}function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_utils=require("../flow/utils.js"),_ContentViews=require("./ContentView/ContentViews"),_ContentErrors=require("./ContentView/ContentErrors"),ContentErrors=_interopRequireWildcard(_ContentErrors),_ContentLoader=require("./ContentView/ContentLoader"),_ContentLoader2=_interopRequireDefault(_ContentLoader),_ViewSelector=require("./ContentView/ViewSelector"),_ViewSelector2=_interopRequireDefault(_ViewSelector),ContentView=function(e){function t(e,r){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,r));return n.state={displayLarge:!1,View:_ContentViews.ViewAuto},n.selectView=n.selectView.bind(n),n}return _inherits(t,e),_createClass(t,[{key:"selectView",value:function(e){this.setState({View:e})}},{key:"displayLarge",value:function(){this.setState({displayLarge:!0})}},{key:"componentWillReceiveProps",value:function(e){e.message!==this.props.message&&this.setState({displayLarge:!1,View:_ContentViews.ViewAuto})}},{key:"isContentTooLarge",value:function(e){return e.contentLength>1048576*(_ContentViews.ViewImage.matches(e)?10:.2)}},{key:"render",value:function(){var e=this.props,t=e.flow,r=e.message,n=this.state,o=n.displayLarge,a=n.View;return 0===r.contentLength?_react2["default"].createElement(ContentErrors.ContentEmpty,this.props):null===r.contentLength?_react2["default"].createElement(ContentErrors.ContentMissing,this.props):!o&&this.isContentTooLarge(r)?_react2["default"].createElement(ContentErrors.ContentTooLarge,_extends({},this.props,{onClick:this.displayLarge})):_react2["default"].createElement("div",null,a.textView?_react2["default"].createElement(_ContentLoader2["default"],{flow:t,message:r},_react2["default"].createElement(this.state.View,{content:""})):_react2["default"].createElement(a,{flow:t,message:r}),_react2["default"].createElement("div",{className:"view-options text-center"},_react2["default"].createElement(_ViewSelector2["default"],{onSelectView:this.selectView,active:a,message:r})," ",_react2["default"].createElement("a",{className:"btn btn-default btn-xs",href:_utils.MessageUtils.getContentURL(t,r)},_react2["default"].createElement("i",{className:"fa fa-download"}))))}}]),t}(_react.Component);ContentView.propTypes={flow:_react2["default"].PropTypes.object.isRequired,message:_react2["default"].PropTypes.object.isRequired},exports["default"]=ContentView; -},{"../ducks/eventLog":32,"./EventLog/EventList":5,"./common":21,"react":"react","react-redux":"react-redux"}],5:[function(require,module,exports){ +},{"../flow/utils.js":50,"./ContentView/ContentErrors":5,"./ContentView/ContentLoader":6,"./ContentView/ContentViews":7,"./ContentView/ViewSelector":8,"react":"react"}],5:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ContentEmpty(e){var t=e.flow,n=e.message;return _react2["default"].createElement("div",{className:"alert alert-info"},"No ",t.request===n?"request":"response"," content.")}function ContentMissing(e){var t=e.flow,n=e.message;return _react2["default"].createElement("div",{className:"alert alert-info"},t.request===n?"Request":"Response"," content missing.")}function ContentTooLarge(e){var t=e.message,n=e.onClick;return _react2["default"].createElement("div",{className:"alert alert-warning"},_react2["default"].createElement("button",{onClick:n,className:"btn btn-xs btn-warning pull-right"},"Display anyway"),(0,_utils.formatSize)(t.contentLength)," content size.")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ContentEmpty=ContentEmpty,exports.ContentMissing=ContentMissing,exports.ContentTooLarge=ContentTooLarge;var _react=require("react"),_react2=_interopRequireDefault(_react),_ContentViews=require("./ContentViews"),_utils=require("../../utils.js"); + +},{"../../utils.js":52,"./ContentViews":7,"react":"react"}],6:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_utils=require("../../flow/utils.js"),ContentLoader=function(e){function t(e,n){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return r.state={content:null,request:null},r}return _inherits(t,e),_createClass(t,[{key:"requestContent",value:function(e){var t=this;this.state.request&&this.state.request.abort();var n=_utils.MessageUtils.getContent(e.flow,e.message);this.setState({content:null,request:n}),n.done(function(e){t.setState({content:e})}).fail(function(e,n,r){"abort"!==n&&t.setState({content:"AJAX Error: "+n+"\r\n"+r})}).always(function(){t.setState({request:null})})}},{key:"componentWillMount",value:function(){this.requestContent(this.props)}},{key:"componentWillReceiveProps",value:function(e){e.message!==this.props.message&&this.requestContent(e)}},{key:"componentWillUnmount",value:function(){this.state.request&&this.state.request.abort()}},{key:"render",value:function(){return this.state.content?_react2["default"].cloneElement(this.props.children,{content:this.state.content}):_react2["default"].createElement("div",{className:"text-center"},_react2["default"].createElement("i",{className:"fa fa-spinner fa-spin"}))}}]),t}(_react.Component);ContentLoader.propTypes={flow:_react.PropTypes.object.isRequired,message:_react.PropTypes.object.isRequired},exports["default"]=ContentLoader; + +},{"../../flow/utils.js":50,"react":"react"}],7:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ViewImage(e){var t=e.flow,i=e.message;return _react2["default"].createElement("div",{className:"flowview-image"},_react2["default"].createElement("img",{src:_utils.MessageUtils.getContentURL(t,i),alt:"preview",className:"img-thumbnail"}))}function ViewRaw(e){var t=e.content;return _react2["default"].createElement("pre",null,t)}function ViewJSON(e){var t=e.content,i=t;try{i=JSON.stringify(JSON.parse(t),null,2)}catch(r){}return _react2["default"].createElement("pre",null,i)}function ViewAuto(e){var t=e.message,i=e.flow,r=ViewAuto.findView(t);return r.textView?_react2["default"].createElement(_ContentLoader2["default"],{message:t,flow:i},_react2["default"].createElement(r,{content:""})):_react2["default"].createElement(r,{message:t,flow:i})}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ViewImage=ViewImage,exports.ViewRaw=ViewRaw,exports.ViewJSON=ViewJSON,exports.ViewAuto=ViewAuto;var _react=require("react"),_react2=_interopRequireDefault(_react),_ContentLoader=require("./ContentLoader"),_ContentLoader2=_interopRequireDefault(_ContentLoader),_utils=require("../../flow/utils.js"),views=[ViewAuto,ViewImage,ViewJSON,ViewRaw];ViewImage.regex=/^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i,ViewImage.matches=function(e){return ViewImage.regex.test(_utils.MessageUtils.getContentType(e))},ViewImage.propTypes={flow:_react.PropTypes.object.isRequired,message:_react.PropTypes.object.isRequired},ViewRaw.textView=!0,ViewRaw.matches=function(){return!0},ViewRaw.propTypes={content:_react2["default"].PropTypes.string.isRequired},ViewJSON.textView=!0,ViewJSON.regex=/^application\/json$/i,ViewJSON.matches=function(e){return ViewJSON.regex.test(_utils.MessageUtils.getContentType(e))},ViewJSON.propTypes={content:_react2["default"].PropTypes.string.isRequired},ViewAuto.matches=function(){return!1},ViewAuto.findView=function(e){return views.find(function(t){return t.matches(e)})||views[views.length-1]},ViewAuto.propTypes={message:_react2["default"].PropTypes.object.isRequired,flow:_react2["default"].PropTypes.object.isRequired},exports["default"]=views; + +},{"../../flow/utils.js":50,"./ContentLoader":6,"react":"react"}],8:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ViewSelector(e){var t=e.active,r=e.message,a=e.onSelectView;return _react2["default"].createElement("div",{className:"view-selector btn-group btn-group-xs"},_ContentViews2["default"].map(function(e){return _react2["default"].createElement("button",{key:e.name,onClick:function(){return a(e)},className:(0,_classnames2["default"])("btn btn-default",{active:e===t})},e===_ContentViews.ViewAuto?"auto: "+_ContentViews.ViewAuto.findView(r).name.toLowerCase().replace("view",""):e.name.toLowerCase().replace("view",""))}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=ViewSelector;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_ContentViews=require("./ContentViews"),_ContentViews2=_interopRequireDefault(_ContentViews);ViewSelector.propTypes={active:_react.PropTypes.func.isRequired,message:_react.PropTypes.object.isRequired,onSelectView:_react.PropTypes.func.isRequired}; + +},{"./ContentViews":7,"classnames":"classnames","react":"react"}],9:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_eventLog=require("../ducks/eventLog"),_ToggleButton=require("./common/ToggleButton"),_ToggleButton2=_interopRequireDefault(_ToggleButton),_EventList=require("./EventLog/EventList"),_EventList2=_interopRequireDefault(_EventList),EventLog=function(e){function t(e,r){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,r));return n.state={height:n.props.defaultHeight},n.onDragStart=n.onDragStart.bind(n),n.onDragMove=n.onDragMove.bind(n),n.onDragStop=n.onDragStop.bind(n),n}return _inherits(t,e),_createClass(t,[{key:"onDragStart",value:function(e){e.preventDefault(),this.dragStart=this.state.height+e.pageY,window.addEventListener("mousemove",this.onDragMove),window.addEventListener("mouseup",this.onDragStop),window.addEventListener("dragend",this.onDragStop)}},{key:"onDragMove",value:function(e){e.preventDefault(),this.setState({height:this.dragStart-e.pageY})}},{key:"onDragStop",value:function(e){e.preventDefault(),window.removeEventListener("mousemove",this.onDragMove)}},{key:"render",value:function(){var e=this.state.height,t=this.props,r=t.filters,n=t.events,o=t.onToggleFilter,a=t.onClose;return _react2["default"].createElement("div",{className:"eventlog",style:{height:e}},_react2["default"].createElement("div",{onMouseDown:this.onDragStart},"Eventlog",_react2["default"].createElement("div",{className:"pull-right"},["debug","info","web"].map(function(e){return _react2["default"].createElement(_ToggleButton2["default"],{key:e,text:e,checked:r[e],onToggle:function(){return o(e)}})}),_react2["default"].createElement("i",{onClick:a,className:"fa fa-close"}))),_react2["default"].createElement(_EventList2["default"],{events:n}))}}]),t}(_react.Component);EventLog.propTypes={filters:_react.PropTypes.object.isRequired,events:_react.PropTypes.array.isRequired,onToggleFilter:_react.PropTypes.func.isRequired,onClose:_react.PropTypes.func.isRequired,defaultHeight:_react.PropTypes.number},EventLog.defaultProps={defaultHeight:200},exports["default"]=(0,_reactRedux.connect)(function(e){return{filters:e.eventLog.filter,events:e.eventLog.filteredEvents}},{onClose:_eventLog.toggleEventLogVisibility,onToggleFilter:_eventLog.toggleEventLogFilter})(EventLog); + +},{"../ducks/eventLog":42,"./EventLog/EventList":10,"./common/ToggleButton":37,"react":"react","react-redux":"react-redux"}],10:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function LogIcon(e){var t=e.event,r={web:"html5",debug:"bug"}[t.level]||"info";return _react2["default"].createElement("i",{className:"fa fa-fw fa-"+r})}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var o=t[r];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,r,o){return r&&e(t.prototype,r),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_shallowequal=require("shallowequal"),_shallowequal2=_interopRequireDefault(_shallowequal),_AutoScroll=require("../helpers/AutoScroll"),_AutoScroll2=_interopRequireDefault(_AutoScroll),_VirtualScroll=require("../helpers/VirtualScroll"),EventLogList=function(e){function t(e){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return r.heights={},r.state={vScroll:(0,_VirtualScroll.calcVScroll)()},r.onViewportUpdate=r.onViewportUpdate.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"componentDidMount",value:function(){window.addEventListener("resize",this.onViewportUpdate),this.onViewportUpdate()}},{key:"componentWillUnmount",value:function(){window.removeEventListener("resize",this.onViewportUpdate)}},{key:"componentDidUpdate",value:function(){this.onViewportUpdate()}},{key:"onViewportUpdate",value:function(){var e=this,t=_reactDom2["default"].findDOMNode(this),r=(0,_VirtualScroll.calcVScroll)({itemCount:this.props.events.length,rowHeight:this.props.rowHeight,viewportTop:t.scrollTop,viewportHeight:t.offsetHeight,itemHeights:this.props.events.map(function(t){return e.heights[t.id]})});(0,_shallowequal2["default"])(this.state.vScroll,r)||this.setState({vScroll:r})}},{key:"setHeight",value:function(e,t){if(t&&!this.heights[e]){var r=t.offsetHeight;this.heights[e]!==r&&(this.heights[e]=r,this.onViewportUpdate())}}},{key:"render",value:function(){var e=this,t=this.state.vScroll,r=this.props.events;return _react2["default"].createElement("pre",{onScroll:this.onViewportUpdate},_react2["default"].createElement("div",{style:{height:t.paddingTop}}),r.slice(t.start,t.end).map(function(t){return _react2["default"].createElement("div",{key:t.id,ref:function(r){return e.setHeight(t.id,r)}},_react2["default"].createElement(LogIcon,{event:t}),t.message)}),_react2["default"].createElement("div",{style:{height:t.paddingBottom}}))}}]),t}(_react.Component);EventLogList.propTypes={events:_react.PropTypes.array.isRequired,rowHeight:_react.PropTypes.number},EventLogList.defaultProps={rowHeight:18},exports["default"]=(0,_AutoScroll2["default"])(EventLogList); -},{"../helpers/AutoScroll":28,"../helpers/VirtualScroll":29,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],6:[function(require,module,exports){ +},{"../helpers/AutoScroll":39,"../helpers/VirtualScroll":40,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],11:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var o=0;o<t.length;o++){var r=t[o];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,o,r){return o&&e(t.prototype,o),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_shallowequal=require("shallowequal"),_shallowequal2=_interopRequireDefault(_shallowequal),_AutoScroll=require("./helpers/AutoScroll"),_AutoScroll2=_interopRequireDefault(_AutoScroll),_VirtualScroll=require("./helpers/VirtualScroll"),_FlowTableHead=require("./FlowTable/FlowTableHead"),_FlowTableHead2=_interopRequireDefault(_FlowTableHead),_FlowRow=require("./FlowTable/FlowRow"),_FlowRow2=_interopRequireDefault(_FlowRow),_filt=require("../filt/filt"),_filt2=_interopRequireDefault(_filt),FlowTable=function(e){function t(e,o){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,o));return r.state={vScroll:(0,_VirtualScroll.calcVScroll)()},r.onViewportUpdate=r.onViewportUpdate.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"componentWillMount",value:function(){window.addEventListener("resize",this.onViewportUpdate)}},{key:"componentWillUnmount",value:function(){window.removeEventListener("resize",this.onViewportUpdate)}},{key:"componentDidUpdate",value:function(){if(this.onViewportUpdate(),this.shouldScrollIntoView){this.shouldScrollIntoView=!1;var e=this.props,t=e.rowHeight,o=e.flows,r=e.selected,l=_reactDom2["default"].findDOMNode(this),a=_reactDom2["default"].findDOMNode(this.refs.head),i=a?a.offsetHeight:0,n=o.indexOf(r)*t+i,u=n+t,c=l.scrollTop,s=l.offsetHeight;c>n-i?l.scrollTop=n-i:u>c+s&&(l.scrollTop=u-s)}}},{key:"componentWillReceiveProps",value:function(e){e.selected&&e.selected!==this.props.selected&&(this.shouldScrollIntoView=!0)}},{key:"onViewportUpdate",value:function(){var e=_reactDom2["default"].findDOMNode(this),t=e.scrollTop,o=(0,_VirtualScroll.calcVScroll)({viewportTop:t,viewportHeight:e.offsetHeight,itemCount:this.props.flows.length,rowHeight:this.props.rowHeight});this.state.viewportTop===t&&(0,_shallowequal2["default"])(this.state.vScroll,o)||this.setState({vScroll:o,viewportTop:t})}},{key:"render",value:function(){var e=this,t=this.state,o=t.vScroll,r=t.viewportTop,l=this.props,a=l.flows,i=l.selected,n=l.highlight,u=n?_filt2["default"].parse(n):function(){return!1};return _react2["default"].createElement("div",{className:"flow-table",onScroll:this.onViewportUpdate},_react2["default"].createElement("table",null,_react2["default"].createElement("thead",{ref:"head",style:{transform:"translateY("+r+"px)"}},_react2["default"].createElement(_FlowTableHead2["default"],null)),_react2["default"].createElement("tbody",null,_react2["default"].createElement("tr",{style:{height:o.paddingTop}}),a.slice(o.start,o.end).map(function(t){return _react2["default"].createElement(_FlowRow2["default"],{key:t.id,flow:t,selected:t===i,highlighted:u(t),onSelect:e.props.onSelect})}),_react2["default"].createElement("tr",{style:{height:o.paddingBottom}}))))}}]),t}(_react2["default"].Component);FlowTable.propTypes={onSelect:_react.PropTypes.func.isRequired,flows:_react.PropTypes.array.isRequired,rowHeight:_react.PropTypes.number,highlight:_react.PropTypes.string,selected:_react.PropTypes.object},FlowTable.defaultProps={rowHeight:32},exports["default"]=(0,_AutoScroll2["default"])(FlowTable); -},{"../filt/filt":39,"./FlowTable/FlowRow":8,"./FlowTable/FlowTableHead":9,"./helpers/AutoScroll":28,"./helpers/VirtualScroll":29,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],7:[function(require,module,exports){ +},{"../filt/filt":49,"./FlowTable/FlowRow":13,"./FlowTable/FlowTableHead":14,"./helpers/AutoScroll":39,"./helpers/VirtualScroll":40,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],12:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function TLSColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:(0,_classnames2["default"])("col-tls","https"===t.request.scheme?"col-tls-https":"col-tls-http")})}function IconColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-icon"},_react2["default"].createElement("div",{className:(0,_classnames2["default"])("resource-icon",IconColumn.getIcon(t))}))}function PathColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-path"},t.request.is_replay&&_react2["default"].createElement("i",{className:"fa fa-fw fa-repeat pull-right"}),t.intercepted&&_react2["default"].createElement("i",{className:"fa fa-fw fa-pause pull-right"}),_utils.RequestUtils.pretty_url(t.request))}function MethodColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-method"},t.request.method)}function StatusColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-status"},t.response&&t.response.status_code)}function SizeColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-size"},(0,_utils2.formatSize)(SizeColumn.getTotalSize(t)))}function TimeColumn(e){var t=e.flow;return _react2["default"].createElement("td",{className:"col-time"},t.response?(0,_utils2.formatTimeDelta)(1e3*(t.response.timestamp_end-t.request.timestamp_start)):"...")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.TLSColumn=TLSColumn,exports.IconColumn=IconColumn,exports.PathColumn=PathColumn,exports.MethodColumn=MethodColumn,exports.StatusColumn=StatusColumn,exports.SizeColumn=SizeColumn,exports.TimeColumn=TimeColumn;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js");TLSColumn.sortKeyFun=function(e){return e.request.scheme},TLSColumn.headerClass="col-tls",TLSColumn.headerName="",IconColumn.headerClass="col-icon",IconColumn.headerName="",IconColumn.getIcon=function(e){if(!e.response)return"resource-icon-plain";var t=_utils.ResponseUtils.getContentType(e.response)||"";return 304===e.response.status_code?"resource-icon-not-modified":300<=e.response.status_code&&e.response.status_code<400?"resource-icon-redirect":t.indexOf("image")>=0?"resource-icon-image":t.indexOf("javascript")>=0?"resource-icon-js":t.indexOf("css")>=0?"resource-icon-css":t.indexOf("html")>=0?"resource-icon-document":"resource-icon-plain"},PathColumn.sortKeyFun=function(e){return _utils.RequestUtils.pretty_url(e.request)},PathColumn.headerClass="col-path",PathColumn.headerName="Path",MethodColumn.sortKeyFun=function(e){return e.request.method},MethodColumn.headerClass="col-method",MethodColumn.headerName="Method",StatusColumn.sortKeyFun=function(e){return e.response&&e.response.status_code},StatusColumn.headerClass="col-status",StatusColumn.headerName="Status",SizeColumn.sortKeyFun=function(e){var t=e.request.contentLength;return e.response&&(t+=e.response.contentLength||0),t},SizeColumn.getTotalSize=SizeColumn.sortKeyFun,SizeColumn.headerClass="col-size",SizeColumn.headerName="Size",TimeColumn.sortKeyFun=function(e){return e.response&&e.response.timestamp_end-e.request.timestamp_start},TimeColumn.headerClass="col-time",TimeColumn.headerName="Time",exports["default"]=[TLSColumn,IconColumn,PathColumn,MethodColumn,StatusColumn,SizeColumn,TimeColumn]; -},{"../../flow/utils.js":40,"../../utils.js":42,"classnames":"classnames","react":"react"}],8:[function(require,module,exports){ +},{"../../flow/utils.js":50,"../../utils.js":52,"classnames":"classnames","react":"react"}],13:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function FlowRow(e){var t=e.flow,r=e.selected,l=e.highlighted,o=e.onSelect,s=(0,_classnames2["default"])({selected:r,highlighted:l,intercepted:t.intercepted,"has-request":t.request,"has-response":t.response});return _react2["default"].createElement("tr",{className:s,onClick:function(){return o(t)}},_FlowColumns2["default"].map(function(e){return _react2["default"].createElement(e,{key:e.name,flow:t})}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=FlowRow;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns);FlowRow.propTypes={onSelect:_react.PropTypes.func.isRequired,flow:_react.PropTypes.object.isRequired,highlighted:_react.PropTypes.bool,selected:_react.PropTypes.bool}; -},{"./FlowColumns":7,"classnames":"classnames","react":"react"}],9:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function FlowTableHead(e){function r(e){s({sortColumn:e.name,sortDesc:e.name!==t?!1:!o})}var t=e.sortColumn,o=e.sortDesc,s=e.onSort,a=o?"sort-desc":"sort-asc";return _react2["default"].createElement("tr",null,_FlowColumns2["default"].map(function(e){return _react2["default"].createElement("th",{className:(0,_classnames2["default"])(e.headerClass,t===e.name&&a),key:e.name,onClick:function(){return r(e)}},e.headerName)}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns),_flows=require("../../ducks/flows");FlowTableHead.propTypes={onSort:_react.PropTypes.func.isRequired,sortDesc:_react2["default"].PropTypes.bool.isRequired,sortColumn:_react2["default"].PropTypes.string},exports["default"]=(0,_reactRedux.connect)(function(e){return{sortDesc:e.flows.sort.sortDesc,sortColumn:e.flows.sort.sortColumn}},{onSort:_flows.setSort})(FlowTableHead); +},{"./FlowColumns":12,"classnames":"classnames","react":"react"}],14:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function FlowTableHead(e){var r=e.sortColumn,t=e.sortDesc,o=e.onSort,s=t?"sort-desc":"sort-asc";return _react2["default"].createElement("tr",null,_FlowColumns2["default"].map(function(e){return _react2["default"].createElement("th",{className:(0,_classnames2["default"])(e.headerClass,r===e.name&&s),key:e.name,onClick:function(){return o({sortColumn:e.name,sortDesc:e.name!==r?!1:!t})}},e.headerName)}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns),_flows=require("../../ducks/flows");FlowTableHead.propTypes={onSort:_react.PropTypes.func.isRequired,sortDesc:_react2["default"].PropTypes.bool.isRequired,sortColumn:_react2["default"].PropTypes.string},exports["default"]=(0,_reactRedux.connect)(function(e){return{sortDesc:e.flows.sort.sortDesc,sortColumn:e.flows.sort.sortColumn}},{onSort:_flows.setSort})(FlowTableHead); + +},{"../../ducks/flows":43,"./FlowColumns":12,"classnames":"classnames","react":"react","react-redux":"react-redux"}],15:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var o=t[r];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,r,o){return r&&e(t.prototype,r),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_Nav=require("./FlowView/Nav"),_Nav2=_interopRequireDefault(_Nav),_Messages=require("./FlowView/Messages"),_Details=require("./FlowView/Details"),_Details2=_interopRequireDefault(_Details),_Prompt=require("./Prompt"),_Prompt2=_interopRequireDefault(_Prompt),FlowView=function(e){function t(e,r){_classCallCheck(this,t);var o=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,r));return o.state={prompt:!1},o.closePrompt=o.closePrompt.bind(o),o.selectTab=o.selectTab.bind(o),o}return _inherits(t,e),_createClass(t,[{key:"getTabs",value:function(){var e=this;return["request","response","error"].filter(function(t){return e.props.flow[t]}).concat(["details"])}},{key:"nextTab",value:function(e){var t=this.getTabs();this.selectTab(t[(t.indexOf(this.props.tab)+e+t.length)%t.length])}},{key:"selectTab",value:function(e){this.props.updateLocation("/flows/"+this.props.flow.id+"/"+e)}},{key:"closePrompt",value:function(e){this.setState({prompt:!1}),e&&this.refs.tab.edit(e)}},{key:"promptEdit",value:function(){var e=void 0;switch(this.props.tab){case"request":e=["method","url",{text:"http version",key:"v"},"header"];break;case"response":e=[{text:"http version",key:"v"},"code","message","header"];break;case"details":return;default:throw"Unknown tab for edit: "+this.props.tab}this.setState({prompt:{options:e,done:this.closePrompt}})}},{key:"render",value:function(){var e=this.getTabs(),r=this.props,o=r.flow,a=r.tab;e.indexOf(a)<0&&(a="response"===a&&o.error?"error":"error"===a&&o.response?"response":e[0]);var s=t.allTabs[_lodash2["default"].capitalize(a)];return _react2["default"].createElement("div",{className:"flow-detail",onScroll:this.adjustHead},_react2["default"].createElement(_Nav2["default"],{flow:o,tabs:e,active:a,onSelectTab:this.selectTab}),_react2["default"].createElement(s,{ref:"tab",flow:o}),this.state.prompt&&_react2["default"].createElement(_Prompt2["default"],this.state.prompt))}}]),t}(_react.Component);FlowView.allTabs={Request:_Messages.Request,Response:_Messages.Response,Error:_Messages.Error,Details:_Details2["default"]},exports["default"]=FlowView; -},{"../../ducks/flows":33,"./FlowColumns":7,"classnames":"classnames","react":"react","react-redux":"react-redux"}],10:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function Footer(e){var a=e.settings;return _react2["default"].createElement("footer",null,a.mode&&"regular"!=a.mode&&_react2["default"].createElement("span",{className:"label label-success"},a.mode," mode"),a.intercept&&_react2["default"].createElement("span",{className:"label label-success"},"Intercept: ",a.intercept),a.showhost&&_react2["default"].createElement("span",{className:"label label-success"},"showhost"),a.no_upstream_cert&&_react2["default"].createElement("span",{className:"label label-success"},"no-upstream-cert"),a.rawtcp&&_react2["default"].createElement("span",{className:"label label-success"},"raw-tcp"),!a.http2&&_react2["default"].createElement("span",{className:"label label-success"},"no-http2"),a.anticache&&_react2["default"].createElement("span",{className:"label label-success"},"anticache"),a.anticomp&&_react2["default"].createElement("span",{className:"label label-success"},"anticomp"),a.stickyauth&&_react2["default"].createElement("span",{className:"label label-success"},"stickyauth: ",a.stickyauth),a.stickycookie&&_react2["default"].createElement("span",{className:"label label-success"},"stickycookie: ",a.stickycookie),a.stream&&_react2["default"].createElement("span",{className:"label label-success"},"stream: ",(0,_utils.formatSize)(a.stream)))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=Footer;var _react=require("react"),_react2=_interopRequireDefault(_react),_utils=require("../utils.js"),_common=require("./common.js");Footer.propTypes={settings:_react2["default"].PropTypes.object.isRequired}; +},{"./FlowView/Details":16,"./FlowView/Messages":18,"./FlowView/Nav":19,"./Prompt":30,"lodash":"lodash","react":"react"}],16:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function TimeStamp(e){var t=e.t,a=e.deltaTo,r=e.title;return t?_react2["default"].createElement("tr",null,_react2["default"].createElement("td",null,r,":"),_react2["default"].createElement("td",null,(0,_utils.formatTimeStamp)(t),a&&_react2["default"].createElement("span",{className:"text-muted"},"(",(0,_utils.formatTimeDelta)(1e3*(t-a)),")"))):_react2["default"].createElement("tr",null)}function ConnectionInfo(e){var t=e.conn;return _react2["default"].createElement("table",{className:"connection-table"},_react2["default"].createElement("tbody",null,_react2["default"].createElement("tr",{key:"address"},_react2["default"].createElement("td",null,"Address:"),_react2["default"].createElement("td",null,t.address.address.join(":"))),t.sni?_react2["default"].createElement("tr",{key:"sni"}):_react2["default"].createElement("tr",{key:"sni"},_react2["default"].createElement("td",null,_react2["default"].createElement("abbr",{title:"TLS Server Name Indication"},"TLS SNI:")),_react2["default"].createElement("td",null,t.sni))))}function CertificateInfo(e){var t=e.flow;return _react2["default"].createElement("div",null,t.client_conn.cert&&[_react2["default"].createElement("h4",{key:"name"},"Client Certificate"),_react2["default"].createElement("pre",{key:"value",style:{maxHeight:100}},t.client_conn.cert)],t.server_conn.cert&&[_react2["default"].createElement("h4",{key:"name"},"Server Certificate"),_react2["default"].createElement("pre",{key:"value",style:{maxHeight:100}},t.server_conn.cert)])}function Timing(e){var t=e.flow,a=t.server_conn,r=t.client_conn,n=t.request,l=t.response,c=[{title:"Server conn. initiated",t:a.timestamp_start,deltaTo:n.timestamp_start},{title:"Server conn. TCP handshake",t:a.timestamp_tcp_setup,deltaTo:n.timestamp_start},{title:"Server conn. SSL handshake",t:a.timestamp_ssl_setup,deltaTo:n.timestamp_start},{title:"Client conn. established",t:r.timestamp_start,deltaTo:n.timestamp_start},{title:"Client conn. SSL handshake",t:r.timestamp_ssl_setup,deltaTo:n.timestamp_start},{title:"First request byte",t:n.timestamp_start},{title:"Request complete",t:n.timestamp_end,deltaTo:n.timestamp_start},l&&{title:"First response byte",t:l.timestamp_start,deltaTo:n.timestamp_start},l&&{title:"Response complete",t:l.timestamp_end,deltaTo:n.timestamp_start}];return _react2["default"].createElement("div",null,_react2["default"].createElement("h4",null,"Timing"),_react2["default"].createElement("table",{className:"timing-table"},_react2["default"].createElement("tbody",null,c.filter(function(e){return e}).sort(function(e,t){return e.t-t.t}).map(function(e){return _react2["default"].createElement(TimeStamp,_extends({key:e.title},e))}))))}function Details(e){var t=e.flow;return _react2["default"].createElement("section",null,_react2["default"].createElement("h4",null,"Client Connection"),_react2["default"].createElement(ConnectionInfo,{conn:t.client_conn}),_react2["default"].createElement("h4",null,"Server Connection"),_react2["default"].createElement(ConnectionInfo,{conn:t.server_conn}),_react2["default"].createElement(CertificateInfo,{flow:t}),_react2["default"].createElement(Timing,{flow:t}))}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];for(var r in a)Object.prototype.hasOwnProperty.call(a,r)&&(e[r]=a[r])}return e};exports.TimeStamp=TimeStamp,exports.ConnectionInfo=ConnectionInfo,exports.CertificateInfo=CertificateInfo,exports.Timing=Timing,exports["default"]=Details;var _react=require("react"),_react2=_interopRequireDefault(_react),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_utils=require("../../utils.js"); -},{"../utils.js":42,"./common.js":21,"react":"react"}],11:[function(require,module,exports){ +},{"../../utils.js":52,"lodash":"lodash","react":"react"}],17:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_ValueEditor=require("../ValueEditor"),_ValueEditor2=_interopRequireDefault(_ValueEditor),_utils=require("../../utils.js"),HeaderEditor=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){return _react2["default"].createElement(_ValueEditor2["default"],_extends({ref:"input"},this.props,{onKeyDown:this.onKeyDown,inline:!0}))}},{key:"focus",value:function(){_reactDom2["default"].findDOMNode(this).focus()}},{key:"onKeyDown",value:function(e){switch(e.keyCode){case _utils.Key.BACKSPACE:var t=window.getSelection().getRangeAt(0);0===t.startOffset&&0===t.endOffset&&this.props.onRemove(e);break;case _utils.Key.TAB:e.shiftKey||this.props.onTab(e)}}}]),t}(_react.Component),Headers=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"onChange",value:function(e,t,r){var n=_.cloneDeep(this.props.message.headers);n[e][t]=r,n[e][0]||n[e][1]||(1===n.length?(n[0][0]="Name",n[0][1]="Value"):(n.splice(e,1),e===n.length&&(this._nextSel=e-1+"-value"))),this.props.onChange(n)}},{key:"edit",value:function(){this.refs["0-key"].focus()}},{key:"onTab",value:function(e,t,r){var n=this.props.message.headers;if(e===n.length-1&&1===t){r.preventDefault();var o=_.cloneDeep(this.props.message.headers);o.push(["Name","Value"]),this.props.onChange(o),this._nextSel=e+1+"-key"}}},{key:"componentDidUpdate",value:function(){this._nextSel&&this.refs[this._nextSel]&&(this.refs[this._nextSel].focus(),this._nextSel=void 0)}},{key:"onRemove",value:function(e,t,r){1===t?(r.preventDefault(),this.refs[e+"-key"].focus()):e>0&&(r.preventDefault(),this.refs[e-1+"-value"].focus())}},{key:"render",value:function(){var e=this,t=this.props.message;return _react2["default"].createElement("table",{className:"header-table"},_react2["default"].createElement("tbody",null,t.headers.map(function(t,r){return _react2["default"].createElement("tr",{key:r},_react2["default"].createElement("td",{className:"header-name"},_react2["default"].createElement(HeaderEditor,{ref:r+"-key",content:t[0],onDone:function(t){return e.onChange(r,0,t)},onRemove:function(t){return e.onRemove(r,0,t)},onTab:function(t){return e.onTab(r,0,t)}}),":"),_react2["default"].createElement("td",{className:"header-value"},_react2["default"].createElement(HeaderEditor,{ref:r+"-value",content:t[1],onDone:function(t){return e.onChange(r,1,t)},onRemove:function(t){return e.onRemove(r,1,t)},onTab:function(t){return e.onTab(r,1,t)}})))})))}}]),t}(_react.Component);Headers.propTypes={onChange:_react.PropTypes.func.isRequired,message:_react.PropTypes.object.isRequired},exports["default"]=Headers; + +},{"../../utils.js":52,"../ValueEditor":32,"react":"react","react-dom":"react-dom"}],18:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function Error(e){var t=e.flow;return _react2["default"].createElement("section",null,_react2["default"].createElement("div",{className:"alert alert-warning"},t.error.msg,_react2["default"].createElement("div",null,_react2["default"].createElement("small",null,(0,_utils2.formatTimeStamp)(t.error.timestamp)))))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.Response=exports.Request=void 0;var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}();exports.Error=Error;var _react=require("react"),_react2=_interopRequireDefault(_react),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_actions=require("../../actions.js"),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js"),_ContentView=require("../ContentView"),_ContentView2=_interopRequireDefault(_ContentView),_ValueEditor=require("../ValueEditor"),_ValueEditor2=_interopRequireDefault(_ValueEditor),_Headers=require("./Headers"),_Headers2=_interopRequireDefault(_Headers),RequestLine=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this.props.flow;return _react2["default"].createElement("div",{className:"first-line request-line"},_react2["default"].createElement(_ValueEditor2["default"],{ref:"method",content:e.request.method,onDone:function(t){return _actions.FlowActions.update(e,{request:{method:t}})},inline:!0})," ",_react2["default"].createElement(_ValueEditor2["default"],{ref:"url",content:_utils.RequestUtils.pretty_url(e.request),onDone:function(t){return _actions.FlowActions.update(e,{request:Object.assign({path:""},(0,_utils.parseUrl)(t))})},isValid:function(e){return!!(0,_utils.parseUrl)(e).host},inline:!0})," ",_react2["default"].createElement(_ValueEditor2["default"],{ref:"httpVersion",content:e.request.http_version,onDone:function(t){return _actions.FlowActions.update(e,{request:{http_version:(0,_utils.parseHttpVersion)(t)}})},isValid:_utils.isValidHttpVersion,inline:!0}))}}]),t}(_react.Component),ResponseLine=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this.props.flow;return _react2["default"].createElement("div",{className:"first-line response-line"},_react2["default"].createElement(_ValueEditor2["default"],{ref:"httpVersion",content:e.response.http_version,onDone:function(t){return _actions.FlowActions.update(e,{response:{http_version:(0,_utils.parseHttpVersion)(t)}})},isValid:_utils.isValidHttpVersion,inline:!0})," ",_react2["default"].createElement(_ValueEditor2["default"],{ref:"code",content:e.response.status_code+"",onDone:function(t){return _actions.FlowActions.update(e,{response:{code:parseInt(t)}})},isValid:function(e){return/^\d+$/.test(e)},inline:!0})," ",_react2["default"].createElement(_ValueEditor2["default"],{ref:"msg",content:e.response.reason,onDone:function(t){return _actions.FlowActions.update(e,{response:{msg:t}})},inline:!0}))}}]),t}(_react.Component),Request=exports.Request=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this.props.flow;return _react2["default"].createElement("section",{className:"request"},_react2["default"].createElement(RequestLine,{ref:"requestLine",flow:e}),_react2["default"].createElement(_Headers2["default"],{ref:"headers",message:e.request,onChange:function(t){return _actions.FlowActions.update(e,{request:{headers:t}})}}),_react2["default"].createElement("hr",null),_react2["default"].createElement(_ContentView2["default"],{flow:e,message:e.request}))}},{key:"edit",value:function(e){switch(e){case"m":this.refs.requestLine.refs.method.focus();break;case"u":this.refs.requestLine.refs.url.focus();break;case"v":this.refs.requestLine.refs.httpVersion.focus();break;case"h":this.refs.headers.edit();break;default:throw new Error("Unimplemented: "+e)}}}]),t}(_react.Component),Response=exports.Response=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this.props.flow;return _react2["default"].createElement("section",{className:"response"},_react2["default"].createElement(ResponseLine,{ref:"responseLine",flow:e}),_react2["default"].createElement(_Headers2["default"],{ref:"headers",message:e.response,onChange:function(t){return _actions.FlowActions.update(e,{response:{headers:t}})}}),_react2["default"].createElement("hr",null),_react2["default"].createElement(_ContentView2["default"],{flow:e,message:e.response}))}},{key:"edit",value:function(e){switch(e){case"c":this.refs.responseLine.refs.status_code.focus();break;case"m":this.refs.responseLine.refs.msg.focus();break;case"v":this.refs.responseLine.refs.httpVersion.focus();break;case"h":this.refs.headers.edit();break;default:throw new Error("'Unimplemented: "+e)}}}]),t}(_react.Component); + +},{"../../actions.js":2,"../../flow/utils.js":50,"../../utils.js":52,"../ContentView":4,"../ValueEditor":32,"./Headers":17,"lodash":"lodash","react":"react"}],19:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function NavAction(e){var t=e.icon,a=e.title,c=e.onClick;return _react2["default"].createElement("a",{title:a,href:"#",className:"nav-action",onClick:function(e){e.preventDefault(),c(e)}},_react2["default"].createElement("i",{className:"fa fa-fw "+t}))}function Nav(e){var t=e.flow,a=e.active,c=e.tabs,r=e.onSelectTab;return _react2["default"].createElement("nav",{className:"nav-tabs nav-tabs-sm"},c.map(function(e){return _react2["default"].createElement("a",{key:e,href:"#",className:(0,_classnames2["default"])({active:a===e}),onClick:function(t){t.preventDefault(),r(e)}},_.capitalize(e))}),_react2["default"].createElement(NavAction,{title:"[d]elete flow",icon:"fa-trash",onClick:function(){return _actions.FlowActions["delete"](t)}}),_react2["default"].createElement(NavAction,{title:"[D]uplicate flow",icon:"fa-copy",onClick:function(){return _actions.FlowActions.duplicate(t)}}),_react2["default"].createElement(NavAction,{disabled:!0,title:"[r]eplay flow",icon:"fa-repeat",onClick:function(){return _actions.FlowActions.replay(t)}}),t.intercepted&&_react2["default"].createElement(NavAction,{title:"[a]ccept intercepted flow",icon:"fa-play",onClick:function(){return _actions.FlowActions.accept(t)}}),t.modified&&_react2["default"].createElement(NavAction,{title:"revert changes to flow [V]",icon:"fa-history",onClick:function(){return _actions.FlowActions.revert(t)}}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=Nav;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_actions=require("../../actions.js");NavAction.propTypes={icon:_react.PropTypes.string.isRequired,title:_react.PropTypes.string.isRequired,onClick:_react.PropTypes.func.isRequired},Nav.propTypes={flow:_react.PropTypes.object.isRequired,active:_react.PropTypes.string.isRequired,tabs:_react.PropTypes.array.isRequired,onSelectTab:_react.PropTypes.func.isRequired}; + +},{"../../actions.js":2,"classnames":"classnames","react":"react"}],20:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function Footer(e){var a=e.settings;return _react2["default"].createElement("footer",null,a.mode&&"regular"!=a.mode&&_react2["default"].createElement("span",{className:"label label-success"},a.mode," mode"),a.intercept&&_react2["default"].createElement("span",{className:"label label-success"},"Intercept: ",a.intercept),a.showhost&&_react2["default"].createElement("span",{className:"label label-success"},"showhost"),a.no_upstream_cert&&_react2["default"].createElement("span",{className:"label label-success"},"no-upstream-cert"),a.rawtcp&&_react2["default"].createElement("span",{className:"label label-success"},"raw-tcp"),!a.http2&&_react2["default"].createElement("span",{className:"label label-success"},"no-http2"),a.anticache&&_react2["default"].createElement("span",{className:"label label-success"},"anticache"),a.anticomp&&_react2["default"].createElement("span",{className:"label label-success"},"anticomp"),a.stickyauth&&_react2["default"].createElement("span",{className:"label label-success"},"stickyauth: ",a.stickyauth),a.stickycookie&&_react2["default"].createElement("span",{className:"label label-success"},"stickycookie: ",a.stickycookie),a.stream&&_react2["default"].createElement("span",{className:"label label-success"},"stream: ",(0,_utils.formatSize)(a.stream)))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=Footer;var _react=require("react"),_react2=_interopRequireDefault(_react),_utils=require("../utils.js");Footer.propTypes={settings:_react2["default"].PropTypes.object.isRequired}; + +},{"../utils.js":52,"react":"react"}],21:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _toConsumableArray(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t<e.length;t++)r[t]=e[t];return r}return Array.from(e)}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_eventLog=require("../ducks/eventLog"),_MainMenu=require("./Header/MainMenu"),_MainMenu2=_interopRequireDefault(_MainMenu),_ViewMenu=require("./Header/ViewMenu"),_ViewMenu2=_interopRequireDefault(_ViewMenu),_OptionMenu=require("./Header/OptionMenu"),_OptionMenu2=_interopRequireDefault(_OptionMenu),_FileMenu=require("./Header/FileMenu"),_FileMenu2=_interopRequireDefault(_FileMenu),_FlowMenu=require("./Header/FlowMenu"),_FlowMenu2=_interopRequireDefault(_FlowMenu),_ui=require("../ducks/ui.js"),Header=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"handleClick",value:function(e,t){t.preventDefault(),this.props.setActiveMenu(e.title)}},{key:"render",value:function(){var e=this,r=this.props,n=r.settings,u=r.updateLocation,a=r.query,i=r.selectedFlow,o=r.activeMenu,l=[].concat(_toConsumableArray(t.entries));i&&l.push(_FlowMenu2["default"]);var c=_.find(l,function(e){return e.title==o});return _react2["default"].createElement("header",null,_react2["default"].createElement("nav",{className:"nav-tabs nav-tabs-lg"},_react2["default"].createElement(_FileMenu2["default"],null),l.map(function(t){return _react2["default"].createElement("a",{key:t.title,href:"#",className:(0,_classnames2["default"])({active:t===c}),onClick:function(r){return e.handleClick(t,r)}},t.title)})),_react2["default"].createElement("div",{className:"menu"},_react2["default"].createElement(c,{settings:n,updateLocation:u,query:a})))}}]),t}(_react.Component);Header.entries=[_MainMenu2["default"],_ViewMenu2["default"],_OptionMenu2["default"]],Header.propTypes={settings:_react.PropTypes.object.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{selectedFlow:e.flows.selected[0],activeMenu:e.ui.activeMenu}},{setActiveMenu:_ui.setActiveMenu})(Header); -},{"../ducks/eventLog":32,"../ducks/ui.js":35,"./Header/FileMenu":12,"./Header/FlowMenu":15,"./Header/MainMenu":16,"./Header/OptionMenu":17,"./Header/ViewMenu":18,"classnames":"classnames","react":"react","react-redux":"react-redux"}],12:[function(require,module,exports){ +},{"../ducks/eventLog":42,"../ducks/ui.js":45,"./Header/FileMenu":22,"./Header/FlowMenu":25,"./Header/MainMenu":26,"./Header/OptionMenu":27,"./Header/ViewMenu":28,"classnames":"classnames","react":"react","react-redux":"react-redux"}],22:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}return function(t,n,a){return n&&e(t.prototype,n),a&&e(t,a),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_actions=require("../../actions.js"),FileMenu=function(e){function t(e,n){_classCallCheck(this,t);var a=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return a.state={show:!1},a.close=a.close.bind(a),a.onFileClick=a.onFileClick.bind(a),a.onNewClick=a.onNewClick.bind(a),a.onOpenClick=a.onOpenClick.bind(a),a.onOpenFile=a.onOpenFile.bind(a),a.onSaveClick=a.onSaveClick.bind(a),a}return _inherits(t,e),_createClass(t,[{key:"close",value:function(){this.setState({show:!1}),document.removeEventListener("click",this.close)}},{key:"onFileClick",value:function(e){e.preventDefault(),this.state.show||(document.addEventListener("click",this.close),this.setState({show:!0}))}},{key:"onNewClick",value:function(e){e.preventDefault(),confirm("Delete all flows?")&&_actions.FlowActions.clear()}},{key:"onOpenClick",value:function(e){e.preventDefault(),this.fileInput.click()}},{key:"onOpenFile",value:function(e){e.preventDefault(),e.target.files.length>0&&(_actions.FlowActions.upload(e.target.files[0]),this.fileInput.value="")}},{key:"onSaveClick",value:function(e){e.preventDefault(),_actions.FlowActions.download()}},{key:"render",value:function(){var e=this;return _react2["default"].createElement("div",{className:(0,_classnames2["default"])("dropdown pull-left",{open:this.state.show})},_react2["default"].createElement("a",{href:"#",className:"special",onClick:this.onFileClick},"mitmproxy"),_react2["default"].createElement("ul",{className:"dropdown-menu",role:"menu"},_react2["default"].createElement("li",null,_react2["default"].createElement("a",{href:"#",onClick:this.onNewClick},_react2["default"].createElement("i",{className:"fa fa-fw fa-file"}),"New")),_react2["default"].createElement("li",null,_react2["default"].createElement("a",{href:"#",onClick:this.onOpenClick},_react2["default"].createElement("i",{className:"fa fa-fw fa-folder-open"}),"Open..."),_react2["default"].createElement("input",{ref:function(t){return e.fileInput=t},className:"hidden",type:"file",onChange:this.onOpenFile})),_react2["default"].createElement("li",null,_react2["default"].createElement("a",{href:"#",onClick:this.onSaveClick},_react2["default"].createElement("i",{className:"fa fa-fw fa-floppy-o"}),"Save...")),_react2["default"].createElement("li",{role:"presentation",className:"divider"}),_react2["default"].createElement("li",null,_react2["default"].createElement("a",{href:"http://mitm.it/",target:"_blank"},_react2["default"].createElement("i",{className:"fa fa-fw fa-external-link"}),"Install Certificates..."))))}}]),t}(_react.Component);exports["default"]=FileMenu; -},{"../../actions.js":2,"classnames":"classnames","react":"react"}],13:[function(require,module,exports){ +},{"../../actions.js":2,"classnames":"classnames","react":"react"}],23:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_jquery=require("jquery"),_jquery2=_interopRequireDefault(_jquery),FilterDocs=function(e){function t(e,r){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,r));return n.state={doc:t.doc},n}return _inherits(t,e),_createClass(t,[{key:"componentWillMount",value:function(){var e=this;t.xhr||(t.xhr=_jquery2["default"].getJSON("/filter-help"),t.xhr.fail(function(){t.xhr=null})),this.state.doc||t.xhr.done(function(r){t.doc=r,e.setState({doc:r})})}},{key:"render",value:function(){var e=this.state.doc;return e?_react2["default"].createElement("table",{className:"table table-condensed"},_react2["default"].createElement("tbody",null,e.commands.map(function(e){return _react2["default"].createElement("tr",{key:e[1]},_react2["default"].createElement("td",null,e[0].replace(" "," ")),_react2["default"].createElement("td",null,e[1]))}),_react2["default"].createElement("tr",{key:"docs-link"},_react2["default"].createElement("td",{colSpan:"2"},_react2["default"].createElement("a",{href:"http://docs.mitmproxy.org/en/stable/features/filters.html",target:"_blank"},_react2["default"].createElement("i",{className:"fa fa-external-link"}),"  mitmproxy docs"))))):_react2["default"].createElement("i",{className:"fa fa-spinner fa-spin"})}}]),t}(_react.Component);FilterDocs.xhr=null,FilterDocs.doc=null,exports["default"]=FilterDocs; -},{"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ +},{"jquery":"jquery","react":"react"}],24:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_utils=require("../../utils.js"),_filt=require("../../filt/filt"),_filt2=_interopRequireDefault(_filt),_FilterDocs=require("./FilterDocs"),_FilterDocs2=_interopRequireDefault(_FilterDocs),FilterInput=function(e){function t(e,n){_classCallCheck(this,t);var o=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return o.state={value:o.props.value,focus:!1,mousefocus:!1},o.onChange=o.onChange.bind(o),o.onFocus=o.onFocus.bind(o),o.onBlur=o.onBlur.bind(o),o.onKeyDown=o.onKeyDown.bind(o),o.onMouseEnter=o.onMouseEnter.bind(o),o.onMouseLeave=o.onMouseLeave.bind(o),o}return _inherits(t,e),_createClass(t,[{key:"componentWillReceiveProps",value:function(e){this.setState({value:e.value})}},{key:"isValid",value:function(e){try{var t=null==e?this.state.value:e;return t&&_filt2["default"].parse(t),!0}catch(n){return!1}}},{key:"getDesc",value:function(){if(!this.state.value)return _react2["default"].createElement(_FilterDocs2["default"],null);try{return _filt2["default"].parse(this.state.value).desc}catch(e){return""+e}}},{key:"onChange",value:function(e){var t=e.target.value;this.setState({value:t}),this.isValid(t)&&this.props.onChange(t)}},{key:"onFocus",value:function(){this.setState({focus:!0})}},{key:"onBlur",value:function(){this.setState({focus:!1})}},{key:"onMouseEnter",value:function(){this.setState({mousefocus:!0})}},{key:"onMouseLeave",value:function(){this.setState({mousefocus:!1})}},{key:"onKeyDown",value:function(e){e.keyCode!==_utils.Key.ESC&&e.keyCode!==_utils.Key.ENTER||(this.blur(),this.setState({mousefocus:!1})),e.stopPropagation()}},{key:"blur",value:function(){_reactDom2["default"].findDOMNode(this.refs.input).blur(),this.context.returnFocus()}},{key:"select",value:function(){_reactDom2["default"].findDOMNode(this.refs.input).select()}},{key:"render",value:function(){var e=this.props,t=e.type,n=e.color,o=e.placeholder,r=this.state,a=r.value,u=r.focus,s=r.mousefocus;return _react2["default"].createElement("div",{className:(0,_classnames2["default"])("filter-input input-group",{"has-error":!this.isValid()})},_react2["default"].createElement("span",{className:"input-group-addon"},_react2["default"].createElement("i",{className:"fa fa-fw fa-"+t,style:{color:n}})),_react2["default"].createElement("input",{type:"text",ref:"input",placeholder:o,className:"form-control",value:a,onChange:this.onChange,onFocus:this.onFocus,onBlur:this.onBlur,onKeyDown:this.onKeyDown}),(u||s)&&_react2["default"].createElement("div",{className:"popover bottom",onMouseEnter:this.onMouseEnter,onMouseLeave:this.onMouseLeave},_react2["default"].createElement("div",{className:"arrow"}),_react2["default"].createElement("div",{className:"popover-content"},this.getDesc())))}}]),t}(_react.Component);FilterInput.contextTypes={returnFocus:_react2["default"].PropTypes.func},exports["default"]=FilterInput; -},{"../../filt/filt":39,"../../utils.js":42,"./FilterDocs":13,"classnames":"classnames","react":"react","react-dom":"react-dom"}],15:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function FlowMenu(e){var t=e.flow;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_common.Button,{disabled:!0,title:"[r]eplay flow",text:"Replay",icon:"fa-repeat",onClick:_actions.FlowActions.replay.bind(null,t)}),_react2["default"].createElement(_common.Button,{title:"[D]uplicate flow",text:"Duplicate",icon:"fa-copy",onClick:_actions.FlowActions.duplicate.bind(null,t)}),_react2["default"].createElement(_common.Button,{title:"[d]elete flow",text:"Delete",icon:"fa-trash",onClick:_actions.FlowActions["delete"].bind(null,t)}),_react2["default"].createElement(_common.Button,{title:"download",text:"Download",icon:"fa-download",onClick:function(){return window.location=_utils.MessageUtils.getContentURL(t,t.response)}})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_common=require("../common.js"),_actions=require("../../actions.js"),_utils=require("../../flow/utils.js"),_reactRedux=require("react-redux");FlowMenu.title="Flow",FlowMenu.propTypes={flow:_react.PropTypes.object.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{flow:e.flows.all.byId[e.flows.selected[0]]}})(FlowMenu); +},{"../../filt/filt":49,"../../utils.js":52,"./FilterDocs":23,"classnames":"classnames","react":"react","react-dom":"react-dom"}],25:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function FlowMenu(e){var t=e.flow;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_Button2["default"],{disabled:!0,title:"[r]eplay flow",text:"Replay",icon:"fa-repeat",onClick:_actions.FlowActions.replay.bind(null,t)}),_react2["default"].createElement(_Button2["default"],{title:"[D]uplicate flow",text:"Duplicate",icon:"fa-copy",onClick:_actions.FlowActions.duplicate.bind(null,t)}),_react2["default"].createElement(_Button2["default"],{title:"[d]elete flow",text:"Delete",icon:"fa-trash",onClick:_actions.FlowActions["delete"].bind(null,t)}),_react2["default"].createElement(_Button2["default"],{title:"download",text:"Download",icon:"fa-download",onClick:function(){return window.location=_utils.MessageUtils.getContentURL(t,t.response)}})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_Button=require("../common/Button"),_Button2=_interopRequireDefault(_Button),_actions=require("../../actions.js"),_utils=require("../../flow/utils.js"),_reactRedux=require("react-redux");FlowMenu.title="Flow",FlowMenu.propTypes={flow:_react.PropTypes.object.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{flow:e.flows.all.byId[e.flows.selected[0]]}})(FlowMenu); -},{"../../actions.js":2,"../../flow/utils.js":40,"../common.js":21,"react":"react","react-redux":"react-redux"}],16:[function(require,module,exports){ +},{"../../actions.js":2,"../../flow/utils.js":50,"../common/Button":35,"react":"react","react-redux":"react-redux"}],26:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_actions=require("../../actions.js"),_FilterInput=require("./FilterInput"),_FilterInput2=_interopRequireDefault(_FilterInput),MainMenu=function(e){function t(e,n){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return r.onSearchChange=r.onSearchChange.bind(r),r.onHighlightChange=r.onHighlightChange.bind(r),r.onInterceptChange=r.onInterceptChange.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"onSearchChange",value:function(e){this.props.updateLocation(void 0,_defineProperty({},_actions.Query.SEARCH,e))}},{key:"onHighlightChange",value:function(e){this.props.updateLocation(void 0,_defineProperty({},_actions.Query.HIGHLIGHT,e))}},{key:"onInterceptChange",value:function(e){_actions.SettingsActions.update({intercept:e})}},{key:"render",value:function(){var e=this.props,t=e.query,n=e.settings,r=t[_actions.Query.SEARCH]||"",a=t[_actions.Query.HIGHLIGHT]||"",o=n.intercept||"";return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_FilterInput2["default"],{ref:"search",placeholder:"Search",type:"search",color:"black",value:r,onChange:this.onSearchChange}),_react2["default"].createElement(_FilterInput2["default"],{ref:"highlight",placeholder:"Highlight",type:"tag",color:"hsl(48, 100%, 50%)",value:a,onChange:this.onHighlightChange}),_react2["default"].createElement(_FilterInput2["default"],{ref:"intercept",placeholder:"Intercept",type:"pause",color:"hsl(208, 56%, 53%)",value:o,onChange:this.onInterceptChange})),_react2["default"].createElement("div",{className:"clearfix"}))}}]),t}(_react.Component);MainMenu.title="Start",MainMenu.route="flows",MainMenu.propTypes={settings:_react2["default"].PropTypes.object.isRequired},exports["default"]=MainMenu; -},{"../../actions.js":2,"./FilterInput":14,"react":"react"}],17:[function(require,module,exports){ -"use strict";function _interopRequireDefault(t){return t&&t.__esModule?t:{"default":t}}function OptionMenu(t){var e=t.settings;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_common.ToggleButton,{text:"showhost",checked:e.showhost,onToggle:function(){return _actions.SettingsActions.update({showhost:!e.showhost})}}),_react2["default"].createElement(_common.ToggleButton,{text:"no_upstream_cert",checked:e.no_upstream_cert,onToggle:function(){return _actions.SettingsActions.update({no_upstream_cert:!e.no_upstream_cert})}}),_react2["default"].createElement(_common.ToggleButton,{text:"rawtcp",checked:e.rawtcp,onToggle:function(){return _actions.SettingsActions.update({rawtcp:!e.rawtcp})}}),_react2["default"].createElement(_common.ToggleButton,{text:"http2",checked:e.http2,onToggle:function(){return _actions.SettingsActions.update({http2:!e.http2})}}),_react2["default"].createElement(_common.ToggleButton,{text:"anticache",checked:e.anticache,onToggle:function(){return _actions.SettingsActions.update({anticache:!e.anticache})}}),_react2["default"].createElement(_common.ToggleButton,{text:"anticomp",checked:e.anticomp,onToggle:function(){return _actions.SettingsActions.update({anticomp:!e.anticomp})}}),_react2["default"].createElement(_common.ToggleInputButton,{name:"stickyauth",placeholder:"Sticky auth filter",checked:!!e.stickyauth,txt:e.stickyauth||"",onToggleChanged:function(t){return _actions.SettingsActions.update({stickyauth:e.stickyauth?null:t})}}),_react2["default"].createElement(_common.ToggleInputButton,{name:"stickycookie",placeholder:"Sticky cookie filter",checked:!!e.stickycookie,txt:e.stickycookie||"",onToggleChanged:function(t){return _actions.SettingsActions.update({stickycookie:e.stickycookie?null:t})}}),_react2["default"].createElement(_common.ToggleInputButton,{name:"stream",placeholder:"stream...",checked:!!e.stream,txt:e.stream||"",inputType:"number",onToggleChanged:function(t){return _actions.SettingsActions.update({stream:e.stream?null:t})}})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=OptionMenu;var _react=require("react"),_react2=_interopRequireDefault(_react),_common=require("../common.js"),_actions=require("../../actions.js");OptionMenu.title="Options",OptionMenu.propTypes={settings:_react.PropTypes.object.isRequired}; +},{"../../actions.js":2,"./FilterInput":24,"react":"react"}],27:[function(require,module,exports){ +"use strict";function _interopRequireDefault(t){return t&&t.__esModule?t:{"default":t}}function OptionMenu(t){var e=t.settings;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_ToggleButton2["default"],{text:"showhost",checked:e.showhost,onToggle:function(){return _actions.SettingsActions.update({showhost:!e.showhost})}}),_react2["default"].createElement(_ToggleButton2["default"],{text:"no_upstream_cert",checked:e.no_upstream_cert,onToggle:function(){return _actions.SettingsActions.update({no_upstream_cert:!e.no_upstream_cert})}}),_react2["default"].createElement(_ToggleButton2["default"],{text:"rawtcp",checked:e.rawtcp,onToggle:function(){return _actions.SettingsActions.update({rawtcp:!e.rawtcp})}}),_react2["default"].createElement(_ToggleButton2["default"],{text:"http2",checked:e.http2,onToggle:function(){return _actions.SettingsActions.update({http2:!e.http2})}}),_react2["default"].createElement(_ToggleButton2["default"],{text:"anticache",checked:e.anticache,onToggle:function(){return _actions.SettingsActions.update({anticache:!e.anticache})}}),_react2["default"].createElement(_ToggleButton2["default"],{text:"anticomp",checked:e.anticomp,onToggle:function(){return _actions.SettingsActions.update({anticomp:!e.anticomp})}}),_react2["default"].createElement(_ToggleInputButton2["default"],{name:"stickyauth",placeholder:"Sticky auth filter",checked:!!e.stickyauth,txt:e.stickyauth||"",onToggleChanged:function(t){return _actions.SettingsActions.update({stickyauth:e.stickyauth?null:t})}}),_react2["default"].createElement(_ToggleInputButton2["default"],{name:"stickycookie",placeholder:"Sticky cookie filter",checked:!!e.stickycookie,txt:e.stickycookie||"",onToggleChanged:function(t){return _actions.SettingsActions.update({stickycookie:e.stickycookie?null:t})}}),_react2["default"].createElement(_ToggleInputButton2["default"],{name:"stream",placeholder:"stream...",checked:!!e.stream,txt:e.stream||"",inputType:"number",onToggleChanged:function(t){return _actions.SettingsActions.update({stream:e.stream?null:t})}})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=OptionMenu;var _react=require("react"),_react2=_interopRequireDefault(_react),_ToggleButton=require("../common/ToggleButton"),_ToggleButton2=_interopRequireDefault(_ToggleButton),_ToggleInputButton=require("../common/ToggleInputButton"),_ToggleInputButton2=_interopRequireDefault(_ToggleInputButton),_actions=require("../../actions.js");OptionMenu.title="Options",OptionMenu.propTypes={settings:_react.PropTypes.object.isRequired}; + +},{"../../actions.js":2,"../common/ToggleButton":37,"../common/ToggleInputButton":38,"react":"react"}],28:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ViewMenu(e){var t=e.visible,r=e.onToggle;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_ToggleButton2["default"],{text:"Show Event Log",checked:t,onToggle:r})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_ToggleButton=require("../common/ToggleButton"),_ToggleButton2=_interopRequireDefault(_ToggleButton),_eventLog=require("../../ducks/eventLog");ViewMenu.title="View",ViewMenu.route="flows",ViewMenu.propTypes={visible:_react.PropTypes.bool.isRequired,onToggle:_react.PropTypes.func.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{visible:e.eventLog.visible}},{onToggle:_eventLog.toggleEventLogVisibility})(ViewMenu); -},{"../../actions.js":2,"../common.js":21,"react":"react"}],18:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ViewMenu(e){var t=e.visible,r=e.onToggle;return _react2["default"].createElement("div",null,_react2["default"].createElement("div",{className:"menu-row"},_react2["default"].createElement(_common.ToggleButton,{text:"Show Event Log",checked:t,onToggle:r})),_react2["default"].createElement("div",{className:"clearfix"}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_common=require("../common.js"),_eventLog=require("../../ducks/eventLog");ViewMenu.title="View",ViewMenu.route="flows",ViewMenu.propTypes={visible:_react.PropTypes.bool.isRequired,onToggle:_react.PropTypes.func.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{visible:e.eventLog.visible}},{onToggle:_eventLog.toggleEventLogVisibility})(ViewMenu); +},{"../../ducks/eventLog":42,"../common/ToggleButton":37,"react":"react","react-redux":"react-redux"}],29:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var l=0;l<t.length;l++){var s=t[l];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(e,s.key,s)}}return function(t,l,s){return l&&e(t.prototype,l),s&&e(t,s),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_actions=require("../actions.js"),_utils=require("../utils.js"),_Splitter=require("./common/Splitter"),_Splitter2=_interopRequireDefault(_Splitter),_FlowTable=require("./FlowTable"),_FlowTable2=_interopRequireDefault(_FlowTable),_FlowView=require("./FlowView"),_FlowView2=_interopRequireDefault(_FlowView),_flows=require("../ducks/flows"),MainView=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"componentWillReceiveProps",value:function(e){e.routeParams.flowId!==(e.selectedFlow||{}).id&&this.props.selectFlow(e.routeParams.flowId),e.location.query[_actions.Query.SEARCH]!==e.filter&&this.props.setFilter(e.location.query[_actions.Query.SEARCH],!1),e.location.query[_actions.Query.HIGHLIGHT]!==e.highlight&&this.props.setHighlight(e.location.query[_actions.Query.HIGHLIGHT],!1)}},{key:"selectFlow",value:function(e){e?this.props.updateLocation("/flows/"+e.id+"/"+(this.props.routeParams.detailTab||"request")):this.props.updateLocation("/flows")}},{key:"selectFlowRelative",value:function(e){var t=this.props,l=t.flows,s=t.routeParams,i=t.selectedFlow,o=0;s.flowId?o=Math.min(Math.max(0,l.indexOf(i)+e),l.length-1):0>e&&(o=l.length-1),this.selectFlow(l[o])}},{key:"onMainKeyDown",value:function(e){var t=this.props.selectedFlow;if(!e.ctrlKey){switch(e.keyCode){case _utils.Key.K:case _utils.Key.UP:this.selectFlowRelative(-1);break;case _utils.Key.J:case _utils.Key.DOWN:this.selectFlowRelative(1);break;case _utils.Key.SPACE:case _utils.Key.PAGE_DOWN:this.selectFlowRelative(10);break;case _utils.Key.PAGE_UP:this.selectFlowRelative(-10);break;case _utils.Key.END:this.selectFlowRelative(1e10);break;case _utils.Key.HOME:this.selectFlowRelative(-1e10);break;case _utils.Key.ESC:this.selectFlow(null);break;case _utils.Key.H:case _utils.Key.LEFT:this.refs.flowDetails&&this.refs.flowDetails.nextTab(-1);break;case _utils.Key.L:case _utils.Key.TAB:case _utils.Key.RIGHT:this.refs.flowDetails&&this.refs.flowDetails.nextTab(1);break;case _utils.Key.C:e.shiftKey&&_actions.FlowActions.clear();break;case _utils.Key.D:t&&(e.shiftKey?_actions.FlowActions.duplicate(t):_actions.FlowActions["delete"](t));break;case _utils.Key.A:e.shiftKey?_actions.FlowActions.accept_all():t&&t.intercepted&&_actions.FlowActions.accept(t);break;case _utils.Key.R:!e.shiftKey&&t&&_actions.FlowActions.replay(t);break;case _utils.Key.V:e.shiftKey&&t&&t.modified&&_actions.FlowActions.revert(t);break;case _utils.Key.E:this.refs.flowDetails&&this.refs.flowDetails.promptEdit();break;case _utils.Key.SHIFT:break;default:return void console.debug("keydown",e.keyCode)}e.preventDefault()}}},{key:"render",value:function(){var e=this,t=this.props,l=t.flows,s=t.selectedFlow,i=t.highlight;t.sort;return _react2["default"].createElement("div",{className:"main-view"},_react2["default"].createElement(_FlowTable2["default"],{ref:"flowTable",flows:l,selected:s,highlight:i,onSelect:function(t){return e.selectFlow(t)}}),s&&[_react2["default"].createElement(_Splitter2["default"],{key:"splitter"}),_react2["default"].createElement(_FlowView2["default"],{key:"flowDetails",ref:"flowDetails",tab:this.props.routeParams.detailTab,query:this.props.query,updateLocation:this.props.updateLocation,flow:s})])}}]),t}(_react.Component);MainView.propTypes={highlight:_react.PropTypes.string,sort:_react.PropTypes.object},exports["default"]=(0,_reactRedux.connect)(function(e){return{flows:e.flows.view,filter:e.flows.filter,sort:e.flows.sort,highlight:e.flows.highlight,selectedFlow:e.flows.all.byId[e.flows.selected[0]]}},{selectFlow:_flows.selectFlow,setFilter:_flows.setFilter,setHighlight:_flows.setHighlight},void 0,{withRef:!0})(MainView); -},{"../../ducks/eventLog":32,"../common.js":21,"react":"react","react-redux":"react-redux"}],19:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var s=0;s<t.length;s++){var l=t[s];l.enumerable=l.enumerable||!1,l.configurable=!0,"value"in l&&(l.writable=!0),Object.defineProperty(e,l.key,l)}}return function(t,s,l){return s&&e(t.prototype,s),l&&e(t,l),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_actions=require("../actions.js"),_utils=require("../utils.js"),_common=require("./common.js"),_FlowTable=require("./FlowTable"),_FlowTable2=_interopRequireDefault(_FlowTable),_index=require("./flowview/index.js"),_index2=_interopRequireDefault(_index),_flows=require("../ducks/flows"),MainView=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,Object.getPrototypeOf(t).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"componentWillReceiveProps",value:function(e){e.routeParams.flowId!==(e.selectedFlow||{}).id&&this.props.selectFlow(e.routeParams.flowId),e.location.query[_actions.Query.SEARCH]!==e.filter&&this.props.setFilter(e.location.query[_actions.Query.SEARCH],!1),e.location.query[_actions.Query.HIGHLIGHT]!==e.highlight&&this.props.setHighlight(e.location.query[_actions.Query.HIGHLIGHT],!1)}},{key:"selectFlow",value:function(e){e?this.props.updateLocation("/flows/"+e.id+"/"+(this.props.routeParams.detailTab||"request")):this.props.updateLocation("/flows")}},{key:"selectFlowRelative",value:function(e){var t=this.props,s=t.flows,l=t.routeParams,i=t.selectedFlow,o=0;l.flowId?o=Math.min(Math.max(0,s.indexOf(i)+e),s.length-1):0>e&&(o=s.length-1),this.selectFlow(s[o])}},{key:"onMainKeyDown",value:function(e){var t=this.props.selectedFlow;if(!e.ctrlKey){switch(e.keyCode){case _utils.Key.K:case _utils.Key.UP:this.selectFlowRelative(-1);break;case _utils.Key.J:case _utils.Key.DOWN:this.selectFlowRelative(1);break;case _utils.Key.SPACE:case _utils.Key.PAGE_DOWN:this.selectFlowRelative(10);break;case _utils.Key.PAGE_UP:this.selectFlowRelative(-10);break;case _utils.Key.END:this.selectFlowRelative(1e10);break;case _utils.Key.HOME:this.selectFlowRelative(-1e10);break;case _utils.Key.ESC:this.selectFlow(null);break;case _utils.Key.H:case _utils.Key.LEFT:this.refs.flowDetails&&this.refs.flowDetails.nextTab(-1);break;case _utils.Key.L:case _utils.Key.TAB:case _utils.Key.RIGHT:this.refs.flowDetails&&this.refs.flowDetails.nextTab(1);break;case _utils.Key.C:e.shiftKey&&_actions.FlowActions.clear();break;case _utils.Key.D:t&&(e.shiftKey?_actions.FlowActions.duplicate(t):_actions.FlowActions["delete"](t));break;case _utils.Key.A:e.shiftKey?_actions.FlowActions.accept_all():t&&t.intercepted&&_actions.FlowActions.accept(t);break;case _utils.Key.R:!e.shiftKey&&t&&_actions.FlowActions.replay(t);break;case _utils.Key.V:e.shiftKey&&t&&t.modified&&_actions.FlowActions.revert(t);break;case _utils.Key.E:this.refs.flowDetails&&this.refs.flowDetails.promptEdit();break;case _utils.Key.SHIFT:break;default:return void console.debug("keydown",e.keyCode)}e.preventDefault()}}},{key:"render",value:function(){var e=this,t=this.props,s=t.flows,l=t.selectedFlow,i=t.highlight;t.sort;return _react2["default"].createElement("div",{className:"main-view"},_react2["default"].createElement(_FlowTable2["default"],{ref:"flowTable",flows:s,selected:l,highlight:i,onSelect:function(t){return e.selectFlow(t)}}),l&&[_react2["default"].createElement(_common.Splitter,{key:"splitter"}),_react2["default"].createElement(_index2["default"],{key:"flowDetails",ref:"flowDetails",tab:this.props.routeParams.detailTab,query:this.props.query,updateLocation:this.props.updateLocation,flow:l})])}}]),t}(_react.Component);MainView.propTypes={highlight:_react.PropTypes.string,sort:_react.PropTypes.object},exports["default"]=(0,_reactRedux.connect)(function(e){return{flows:e.flows.view,filter:e.flows.filter,sort:e.flows.sort,highlight:e.flows.highlight,selectedFlow:e.flows.all.byId[e.flows.selected[0]]}},{selectFlow:_flows.selectFlow,setFilter:_flows.setFilter,setHighlight:_flows.setHighlight},void 0,{withRef:!0})(MainView); +},{"../actions.js":2,"../ducks/flows":43,"../utils.js":52,"./FlowTable":11,"./FlowView":15,"./common/Splitter":36,"react":"react","react-redux":"react-redux"}],30:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function Prompt(e,t){function r(e){return _lodash2["default"].map(i,"key").includes(e)}function o(e){e.stopPropagation(),e.preventDefault();var r=i.find(function(t){return _utils.Key[t.key.toUpperCase()]===e.keyCode});(r||e.keyCode===_utils.Key.ESC||e.keyCode===_utils.Key.ENTER)&&(n(k||!1),t.returnFocus())}for(var a=e.prompt,n=e.done,u=e.options,i=[],s=0;s<u.length;s++){var l=u[s];if(_lodash2["default"].isString(l)){for(var p=l;p.length>0&&r(p[0]);)p=p.substr(1);l={text:l,key:p[0]}}if(!l.text||!l.key||r(l.key))throw"invalid options";i.push(l)}return _react2["default"].createElement("div",{tabIndex:"0",onKeyDown:o,onClick:onClick,className:"prompt-dialog"},_react2["default"].createElement("div",{className:"prompt-content"},a||_react2["default"].createElement("strong",null,"Select: "),i.map(function(e){function t(t){n(e.key),t.stopPropagation()}var r=e.text.indexOf(e.key);return _react2["default"].createElement("span",{key:e.key,className:"option",onClick:t},-1!==r?e.text.substring(0,r):e.text+"(",prefix,_react2["default"].createElement("strong",{className:"text-primary"},e.key),-1!==r?e.text.substring(r+1):")")})))}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=Prompt;var _react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_utils=require("../utils.js");Prompt.contextTypes={returnFocus:_react.PropTypes.func},Prompt.propTypes={options:_react.PropTypes.array.isRequired,done:_react.PropTypes.func.isRequired,prompt:_react.PropTypes.string}; -},{"../actions.js":2,"../ducks/flows":33,"../utils.js":42,"./FlowTable":6,"./common.js":21,"./flowview/index.js":25,"react":"react","react-redux":"react-redux"}],20:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_reactRedux=require("react-redux"),_common=require("./common.js"),_websocket=require("../ducks/websocket"),_Header=require("./Header"),_Header2=_interopRequireDefault(_Header),_EventLog=require("./EventLog"),_EventLog2=_interopRequireDefault(_EventLog),_Footer=require("./Footer"),_Footer2=_interopRequireDefault(_Footer),_store=require("../store/store.js"),_utils=require("../utils.js"),ProxyAppMain=function(e){function t(e,n){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return r.settingsStore=new _store.SettingsStore,_lodash2["default"].extend(r.settingsStore.dict,{}),r.state={settings:r.settingsStore.dict},r.focus=r.focus.bind(r),r.onKeyDown=r.onKeyDown.bind(r),r.updateLocation=r.updateLocation.bind(r),r.onSettingsChange=r.onSettingsChange.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"componentWillMount",value:function(){this.props.wsConnect()}},{key:"updateLocation",value:function(e,t){void 0===e&&(e=this.props.location.pathname);var n=this.props.location.query,r=!0,o=!1,i=void 0;try{for(var a,s=Object.keys(t||{})[Symbol.iterator]();!(r=(a=s.next()).done);r=!0){var u=a.value;n[u]=t[u]||void 0}}catch(c){o=!0,i=c}finally{try{!r&&s["return"]&&s["return"]()}finally{if(o)throw i}}this.context.router.replace({pathname:e,query:n})}},{key:"getQuery",value:function(){return _lodash2["default"].clone(this.props.location.query)}},{key:"componentDidMount",value:function(){this.focus(),this.settingsStore.addListener("recalculate",this.onSettingsChange)}},{key:"componentWillUnmount",value:function(){this.settingsStore.removeListener("recalculate",this.onSettingsChange)}},{key:"onSettingsChange",value:function(){this.setState({settings:this.settingsStore.dict})}},{key:"getChildContext",value:function(){return{returnFocus:this.focus,location:this.props.location}}},{key:"focus",value:function(){document.activeElement.blur(),window.getSelection().removeAllRanges(),_reactDom2["default"].findDOMNode(this).focus()}},{key:"onKeyDown",value:function(e){var t=this,n=null;switch(e.keyCode){case _utils.Key.I:n="intercept";break;case _utils.Key.L:n="search";break;case _utils.Key.H:n="highlight";break;default:var r=this.refs.view;return this.refs.view.getWrappedInstance&&(r=this.refs.view.getWrappedInstance()),void(r.onMainKeyDown&&r.onMainKeyDown(e))}n&&!function(){var e=t.refs.header;e.setState({active:_Header2["default"].entries.MainMenu},function(){e.refs.active.refs[n].select()})}(),e.preventDefault()}},{key:"render",value:function(){var e=this.props,t=e.showEventLog,n=e.location,r=e.children,o=this.state.settings,i=this.getQuery();return _react2["default"].createElement("div",{id:"container",tabIndex:"0",onKeyDown:this.onKeyDown},_react2["default"].createElement(_Header2["default"],{ref:"header",settings:o,updateLocation:this.updateLocation,query:i}),_react2["default"].cloneElement(r,{ref:"view",location:n,query:i,updateLocation:this.updateLocation}),t&&[_react2["default"].createElement(_common.Splitter,{key:"splitter",axis:"y"}),_react2["default"].createElement(_EventLog2["default"],{key:"eventlog"})],_react2["default"].createElement(_Footer2["default"],{settings:o}))}}]),t}(_react.Component);ProxyAppMain.childContextTypes={returnFocus:_react.PropTypes.func.isRequired,location:_react.PropTypes.object.isRequired},ProxyAppMain.contextTypes={router:_react.PropTypes.object.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{showEventLog:e.eventLog.visible}},{wsConnect:_websocket.connect})(ProxyAppMain); +},{"../utils.js":52,"lodash":"lodash","react":"react","react-dom":"react-dom"}],31:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_reactRedux=require("react-redux"),_websocket=require("../ducks/websocket"),_Header=require("./Header"),_Header2=_interopRequireDefault(_Header),_EventLog=require("./EventLog"),_EventLog2=_interopRequireDefault(_EventLog),_Footer=require("./Footer"),_Footer2=_interopRequireDefault(_Footer),_store=require("../store/store.js"),_utils=require("../utils.js"),ProxyAppMain=function(e){function t(e,n){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return r.settingsStore=new _store.SettingsStore,_lodash2["default"].extend(r.settingsStore.dict,{}),r.state={settings:r.settingsStore.dict},r.focus=r.focus.bind(r),r.onKeyDown=r.onKeyDown.bind(r),r.updateLocation=r.updateLocation.bind(r),r.onSettingsChange=r.onSettingsChange.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"componentWillMount",value:function(){this.props.wsConnect()}},{key:"updateLocation",value:function(e,t){void 0===e&&(e=this.props.location.pathname);var n=this.props.location.query,r=!0,o=!1,i=void 0;try{for(var a,s=Object.keys(t||{})[Symbol.iterator]();!(r=(a=s.next()).done);r=!0){var u=a.value;n[u]=t[u]||void 0}}catch(c){o=!0,i=c}finally{try{!r&&s["return"]&&s["return"]()}finally{if(o)throw i}}this.context.router.replace({pathname:e,query:n})}},{key:"getQuery",value:function(){return _lodash2["default"].clone(this.props.location.query)}},{key:"componentDidMount",value:function(){this.focus(),this.settingsStore.addListener("recalculate",this.onSettingsChange)}},{key:"componentWillUnmount",value:function(){this.settingsStore.removeListener("recalculate",this.onSettingsChange)}},{key:"onSettingsChange",value:function(){this.setState({settings:this.settingsStore.dict})}},{key:"getChildContext",value:function(){return{returnFocus:this.focus}}},{key:"focus",value:function(){document.activeElement.blur(),window.getSelection().removeAllRanges(),_reactDom2["default"].findDOMNode(this).focus()}},{key:"onKeyDown",value:function(e){var t=this,n=null;switch(e.keyCode){case _utils.Key.I:n="intercept";break;case _utils.Key.L:n="search";break;case _utils.Key.H:n="highlight";break;default:var r=this.refs.view;return this.refs.view.getWrappedInstance&&(r=this.refs.view.getWrappedInstance()),void(r.onMainKeyDown&&r.onMainKeyDown(e))}n&&!function(){var e=t.refs.header;e.setState({active:_Header2["default"].entries[0]},function(){e.refs.active.refs[n].select()})}(),e.preventDefault()}},{key:"render",value:function(){var e=this.props,t=e.showEventLog,n=e.location,r=e.children,o=this.state.settings,i=this.getQuery();return _react2["default"].createElement("div",{id:"container",tabIndex:"0",onKeyDown:this.onKeyDown},_react2["default"].createElement(_Header2["default"],{ref:"header",settings:o,updateLocation:this.updateLocation,query:i}),_react2["default"].cloneElement(r,{ref:"view",location:n,query:i,updateLocation:this.updateLocation}),t&&_react2["default"].createElement(_EventLog2["default"],{key:"eventlog"}),_react2["default"].createElement(_Footer2["default"],{settings:o}))}}]),t}(_react.Component);ProxyAppMain.childContextTypes={returnFocus:_react.PropTypes.func.isRequired},ProxyAppMain.contextTypes={router:_react.PropTypes.object.isRequired},exports["default"]=(0,_reactRedux.connect)(function(e){return{showEventLog:e.eventLog.visible}},{wsConnect:_websocket.connect})(ProxyAppMain); -},{"../ducks/websocket":38,"../store/store.js":41,"../utils.js":42,"./EventLog":4,"./Footer":10,"./Header":11,"./common.js":21,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],21:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ToggleInputButton=exports.Button=exports.ToggleButton=exports.Splitter=void 0;var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_utils=require("../utils.js"),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),Splitter=exports.Splitter=_react2["default"].createClass({displayName:"Splitter",getDefaultProps:function(){return{axis:"x"}},getInitialState:function(){return{applied:!1,startX:!1,startY:!1}},onMouseDown:function(e){this.setState({startX:e.pageX,startY:e.pageY}),window.addEventListener("mousemove",this.onMouseMove),window.addEventListener("mouseup",this.onMouseUp),window.addEventListener("dragend",this.onDragEnd)},onDragEnd:function(){_reactDom2["default"].findDOMNode(this).style.transform="",window.removeEventListener("dragend",this.onDragEnd),window.removeEventListener("mouseup",this.onMouseUp),window.removeEventListener("mousemove",this.onMouseMove)},onMouseUp:function(e){this.onDragEnd();var t,n=_reactDom2["default"].findDOMNode(this),r=n.previousElementSibling,o=n.nextElementSibling,a=e.pageX-this.state.startX,s=e.pageY-this.state.startY;t="x"===this.props.axis?r.offsetWidth+a:r.offsetHeight+s,r.style.flex="0 0 "+Math.max(0,t)+"px",o.style.flex="1 1 auto",this.setState({applied:!0}),this.onResize()},onMouseMove:function(e){var t=0,n=0;"x"===this.props.axis?t=e.pageX-this.state.startX:n=e.pageY-this.state.startY,_reactDom2["default"].findDOMNode(this).style.transform="translate("+t+"px,"+n+"px)"},onResize:function(){window.setTimeout(function(){window.dispatchEvent(new CustomEvent("resize"))},1)},reset:function(e){if(this.state.applied){var t=_reactDom2["default"].findDOMNode(this),n=t.previousElementSibling,r=t.nextElementSibling;n.style.flex="",r.style.flex="",e||this.setState({applied:!1}),this.onResize()}},componentWillUnmount:function(){this.reset(!0)},render:function(){var e="splitter";return e+="x"===this.props.axis?" splitter-x":" splitter-y",_react2["default"].createElement("div",{className:e},_react2["default"].createElement("div",{onMouseDown:this.onMouseDown,draggable:"true"}))}}),ToggleButton=exports.ToggleButton=function(e){var t=e.checked,n=e.onToggle,r=e.text;return _react2["default"].createElement("div",{className:"btn btn-toggle "+(t?"btn-primary":"btn-default"),onClick:n},_react2["default"].createElement("i",{className:"fa fa-fw "+(t?"fa-check-square-o":"fa-square-o")})," ",r)};ToggleButton.propTypes={checked:_react2["default"].PropTypes.bool.isRequired,onToggle:_react2["default"].PropTypes.func.isRequired,text:_react2["default"].PropTypes.string.isRequired};var Button=exports.Button=function(e){var t=e.onClick,n=e.text,r=e.icon;return _react2["default"].createElement("div",{className:"btn btn-default",onClick:t},_react2["default"].createElement("i",{className:"fa fa-fw "+r})," ",n)};Button.propTypes={onClick:_react2["default"].PropTypes.func.isRequired,text:_react2["default"].PropTypes.string.isRequired};var ToggleInputButton=exports.ToggleInputButton=function(e){function t(e){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return n.state={txt:e.txt},n}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this;return _react2["default"].createElement("div",{className:"input-group toggle-input-btn"},_react2["default"].createElement("span",{className:"input-group-btn",onClick:function(){return e.props.onToggleChanged(e.state.txt)}},_react2["default"].createElement("div",{className:"btn "+(this.props.checked?"btn-primary":"btn-default")},_react2["default"].createElement("span",{className:"fa "+(this.props.checked?"fa-check-square-o":"fa-square-o")})," ",this.props.name)),_react2["default"].createElement("input",{className:"form-control",placeholder:this.props.placeholder,disabled:this.props.checked,value:this.state.txt,type:this.props.inputType,onChange:function(t){return e.setState({txt:t.target.value})},onKeyDown:function(t){t.keyCode===_utils.Key.ENTER&&e.props.onToggleChanged(e.state.txt),t.stopPropagation()}}))}}]),t}(_react2["default"].Component);ToggleInputButton.propTypes={name:_react2["default"].PropTypes.string.isRequired,txt:_react2["default"].PropTypes.string.isRequired,onToggleChanged:_react2["default"].PropTypes.func.isRequired}; +},{"../ducks/websocket":48,"../store/store.js":51,"../utils.js":52,"./EventLog":9,"./Footer":20,"./Header":21,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],32:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o])}return e},_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var o=t[r];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,r,o){return r&&e(t.prototype,r),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_ValidateEditor=require("./ValueEditor/ValidateEditor"),_ValidateEditor2=_interopRequireDefault(_ValidateEditor),ValueEditor=function(e){function t(e){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return r.focus=r.focus.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"render",value:function(){var e=this,t=this.props.inline?"span":"div";return _react2["default"].createElement(_ValidateEditor2["default"],_extends({},this.props,{onStop:function(){return e.context.returnFocus()},tag:t}))}},{key:"focus",value:function(){_reactDom2["default"].findDOMNode(this).focus()}}]),t}(_react.Component);ValueEditor.contextTypes={returnFocus:_react.PropTypes.func},ValueEditor.propTypes={content:_react.PropTypes.string.isRequired,onDone:_react.PropTypes.func.isRequired,inline:_react.PropTypes.bool},exports["default"]=ValueEditor; -},{"../utils.js":42,"lodash":"lodash","react":"react","react-dom":"react-dom"}],22:[function(require,module,exports){ -"use strict";function _interopRequireDefault(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ValueEditor=void 0;var _extends=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var o=arguments[e];for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&(t[n]=o[n])}return t},_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_utils=require("../utils.js"),contentToHtml=function(t){return _.escape(t)},nodeToContent=function(t){return t.textContent},EditorBase=_react2["default"].createClass({displayName:"EditorBase",propTypes:{content:_react2["default"].PropTypes.string.isRequired,onDone:_react2["default"].PropTypes.func.isRequired,contentToHtml:_react2["default"].PropTypes.func,nodeToContent:_react2["default"].PropTypes.func,onStop:_react2["default"].PropTypes.func,submitOnEnter:_react2["default"].PropTypes.bool,className:_react2["default"].PropTypes.string,tag:_react2["default"].PropTypes.string},getDefaultProps:function(){return{contentToHtml:contentToHtml,nodeToContent:nodeToContent,submitOnEnter:!0,className:"",tag:"div"}},getInitialState:function(){return{editable:!1}},render:function(){var t="inline-input "+this.props.className,e={__html:this.props.contentToHtml(this.props.content)},o=this.props.tag;return _react2["default"].createElement(o,_extends({},this.props,{tabIndex:"0",className:t,contentEditable:this.state.editable||void 0,onFocus:this.onFocus,onMouseDown:this.onMouseDown,onClick:this.onClick,onBlur:this._stop,onKeyDown:this.onKeyDown,onInput:this.onInput,onPaste:this.onPaste,dangerouslySetInnerHTML:e}))},onPaste:function(t){t.preventDefault();var e=t.clipboardData.getData("text/plain");document.execCommand("insertHTML",!1,e)},onMouseDown:function(t){this._mouseDown=!0,window.addEventListener("mouseup",this.onMouseUp),this.props.onMouseDown&&this.props.onMouseDown(t)},onMouseUp:function(){this._mouseDown&&(this._mouseDown=!1,window.removeEventListener("mouseup",this.onMouseUp))},onClick:function(t){this.onMouseUp(),this.onFocus(t)},onFocus:function(t){if(console.log("onFocus",this._mouseDown,this._ignore_events,this.state.editable),!(this._mouseDown||this._ignore_events||this.state.editable)){var e,o=window.getSelection();if(o.rangeCount>0)e=o.getRangeAt(0);else if(document.caretPositionFromPoint&&t.clientX&&t.clientY){var n=document.caretPositionFromPoint(t.clientX,t.clientY);e=document.createRange(),e.setStart(n.offsetNode,n.offset)}else document.caretRangeFromPoint&&t.clientX&&t.clientY?e=document.caretRangeFromPoint(t.clientX,t.clientY):(e=document.createRange(),e.selectNodeContents(_reactDom2["default"].findDOMNode(this)));this._ignore_events=!0,this.setState({editable:!0},function(){var t=_reactDom2["default"].findDOMNode(this);t.blur(),t.focus(),this._ignore_events=!1})}},stop:function(){_reactDom2["default"].findDOMNode(this).blur(),this.props.onStop&&this.props.onStop()},_stop:function(t){if(!this._ignore_events){console.log("_stop",_.extend({},t)),window.getSelection().removeAllRanges();var e=_reactDom2["default"].findDOMNode(this),o=this.props.nodeToContent(e);this.setState({editable:!1}),this.props.onDone(o),this.props.onBlur&&this.props.onBlur(t)}},reset:function(){_reactDom2["default"].findDOMNode(this).innerHTML=this.props.contentToHtml(this.props.content)},onKeyDown:function(t){switch(t.stopPropagation(),t.keyCode){case _utils.Key.ESC:t.preventDefault(),this.reset(),this.stop();break;case _utils.Key.ENTER:this.props.submitOnEnter&&!t.shiftKey&&(t.preventDefault(),this.stop())}},onInput:function(){var t=_reactDom2["default"].findDOMNode(this),e=this.props.nodeToContent(t);this.props.onInput&&this.props.onInput(e)}}),ValidateEditor=_react2["default"].createClass({displayName:"ValidateEditor",propTypes:{content:_react2["default"].PropTypes.string.isRequired,onDone:_react2["default"].PropTypes.func.isRequired,onInput:_react2["default"].PropTypes.func,isValid:_react2["default"].PropTypes.func,className:_react2["default"].PropTypes.string},getInitialState:function(){return{currentContent:this.props.content}},componentWillReceiveProps:function(){this.setState({currentContent:this.props.content})},onInput:function(t){this.setState({currentContent:t}),this.props.onInput&&this.props.onInput(t)},render:function(){var t=this.props.className||"";return this.props.isValid&&(t+=this.props.isValid(this.state.currentContent)?" has-success":" has-warning"),_react2["default"].createElement(EditorBase,_extends({},this.props,{ref:"editor",className:t,onDone:this.onDone,onInput:this.onInput}))},onDone:function(t){this.props.isValid&&!this.props.isValid(t)&&(this.refs.editor.reset(),t=this.props.content),this.props.onDone(t)}}),ValueEditor=exports.ValueEditor=_react2["default"].createClass({displayName:"ValueEditor",contextTypes:{returnFocus:_react2["default"].PropTypes.func},propTypes:{content:_react2["default"].PropTypes.string.isRequired,onDone:_react2["default"].PropTypes.func.isRequired,inline:_react2["default"].PropTypes.bool},render:function(){var t=this.props.inline?"span":"div";return _react2["default"].createElement(ValidateEditor,_extends({},this.props,{onStop:this.onStop,tag:t}))},focus:function(){_reactDom2["default"].findDOMNode(this).focus()},onStop:function(){this.context.returnFocus()}}); +},{"./ValueEditor/ValidateEditor":34,"react":"react","react-dom":"react-dom"}],33:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},_createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_utils=require("../../utils.js"),EditorBase=function(e){function t(e){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return n.state={editable:!1},n.onPaste=n.onPaste.bind(n),n.onMouseDown=n.onMouseDown.bind(n),n.onMouseUp=n.onMouseUp.bind(n),n.onFocus=n.onFocus.bind(n),n.onClick=n.onClick.bind(n),n.stop=n.stop.bind(n),n.onBlur=n.onBlur.bind(n),n.reset=n.reset.bind(n),n.onKeyDown=n.onKeyDown.bind(n),n.onInput=n.onInput.bind(n),n}return _inherits(t,e),_createClass(t,[{key:"stop",value:function(){_reactDom2["default"].findDOMNode(this).blur(),this.props.onStop()}},{key:"render",value:function(){return _react2["default"].createElement(this.props.tag,_extends({},this.props,{tabIndex:"0",className:"inline-input "+this.props.className,contentEditable:this.state.editable||void 0,onFocus:this.onFocus,onMouseDown:this.onMouseDown,onClick:this.onClick,onBlur:this.onBlur,onKeyDown:this.onKeyDown,onInput:this.onInput,onPaste:this.onPaste,dangerouslySetInnerHTML:{__html:this.props.contentToHtml(this.props.content)}}))}},{key:"onPaste",value:function(e){e.preventDefault();var t=e.clipboardData.getData("text/plain");document.execCommand("insertHTML",!1,t)}},{key:"onMouseDown",value:function(e){this._mouseDown=!0,window.addEventListener("mouseup",this.onMouseUp),this.props.onMouseDown(e)}},{key:"onMouseUp",value:function(){this._mouseDown&&(this._mouseDown=!1,window.removeEventListener("mouseup",this.onMouseUp))}},{key:"onClick",value:function(e){this.onMouseUp(),this.onFocus(e)}},{key:"onFocus",value:function(e){var t=this;if(!(this._mouseDown||this._ignore_events||this.state.editable)){var n=window.getSelection(),o=void 0;if(n.rangeCount>0)o=n.getRangeAt(0);else if(document.caretPositionFromPoint&&e.clientX&&e.clientY){var s=document.caretPositionFromPoint(e.clientX,e.clientY);o=document.createRange(),o.setStart(s.offsetNode,s.offset)}else document.caretRangeFromPoint&&e.clientX&&e.clientY?o=document.caretRangeFromPoint(e.clientX,e.clientY):(o=document.createRange(),o.selectNodeContents(_reactDom2["default"].findDOMNode(this)));this._ignore_events=!0,this.setState({editable:!0},function(){var e=_reactDom2["default"].findDOMNode(t);e.blur(),e.focus(),t._ignore_events=!1})}}},{key:"onBlur",value:function(e){this._ignore_events||(window.getSelection().removeAllRanges(),this.setState({editable:!1}),this.props.onDone(this.props.nodeToContent(_reactDom2["default"].findDOMNode(this))),this.props.onBlur(e))}},{key:"reset",value:function(){_reactDom2["default"].findDOMNode(this).innerHTML=this.props.contentToHtml(this.props.content)}},{key:"onKeyDown",value:function(e){switch(e.stopPropagation(),e.keyCode){case _utils.Key.ESC:e.preventDefault(),this.reset(),this.stop();break;case _utils.Key.ENTER:this.props.submitOnEnter&&!e.shiftKey&&(e.preventDefault(),this.stop())}}},{key:"onInput",value:function(){this.props.onInput(this.props.nodeToContent(_reactDom2["default"].findDOMNode(this)))}}]),t}(_react.Component);EditorBase.propTypes={content:_react.PropTypes.string.isRequired,onDone:_react.PropTypes.func.isRequired,contentToHtml:_react.PropTypes.func,nodeToContent:_react.PropTypes.func,onStop:_react.PropTypes.func,submitOnEnter:_react.PropTypes.bool,className:_react.PropTypes.string,tag:_react.PropTypes.string},EditorBase.defaultProps={contentToHtml:function(e){return _.escape(e)},nodeToContent:function(e){return e.textContent},submitOnEnter:!0,className:"",tag:"div",onStop:_.noop,onMouseDown:_.noop,onBlur:_.noop,onInput:_.noop},exports["default"]=EditorBase; -},{"../utils.js":42,"react":"react","react-dom":"react-dom"}],23:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];for(var s in a)Object.prototype.hasOwnProperty.call(a,s)&&(e[s]=a[s])}return e},_react=require("react"),_react2=_interopRequireDefault(_react),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js"),ViewImage=_react2["default"].createClass({displayName:"ViewImage",propTypes:{flow:_react2["default"].PropTypes.object.isRequired,message:_react2["default"].PropTypes.object.isRequired},statics:{regex:/^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i,matches:function(e){return ViewImage.regex.test(_utils.MessageUtils.getContentType(e))}},render:function(){var e=_utils.MessageUtils.getContentURL(this.props.flow,this.props.message);return _react2["default"].createElement("div",{className:"flowview-image"},_react2["default"].createElement("img",{src:e,alt:"preview",className:"img-thumbnail"}))}}),ContentLoader=_react2["default"].createClass({displayName:"ContentLoader",propTypes:{flow:_react2["default"].PropTypes.object.isRequired,message:_react2["default"].PropTypes.object.isRequired},getInitialState:function(){return{content:void 0,request:void 0}},requestContent:function(e){this.state.request&&this.state.request.abort();var t=_utils.MessageUtils.getContent(e.flow,e.message);this.setState({content:void 0,request:t}),t.done(function(e){this.setState({content:e})}.bind(this)).fail(function(e,t,a){"abort"!==t&&this.setState({content:"AJAX Error: "+t+"\r\n"+a})}.bind(this)).always(function(){this.setState({request:void 0})}.bind(this))},componentWillMount:function(){this.requestContent(this.props)},componentWillReceiveProps:function(e){e.message!==this.props.message&&this.requestContent(e)},componentWillUnmount:function(){this.state.request&&this.state.request.abort()},render:function(){return this.state.content?_react2["default"].cloneElement(this.props.children,{content:this.state.content}):_react2["default"].createElement("div",{className:"text-center"},_react2["default"].createElement("i",{className:"fa fa-spinner fa-spin"}))}}),ViewRaw=_react2["default"].createClass({displayName:"ViewRaw",propTypes:{content:_react2["default"].PropTypes.string.isRequired},statics:{textView:!0,matches:function(e){return!0}},render:function(){return _react2["default"].createElement("pre",null,this.props.content)}}),ViewJSON=_react2["default"].createClass({displayName:"ViewJSON",propTypes:{content:_react2["default"].PropTypes.string.isRequired},statics:{textView:!0,regex:/^application\/json$/i,matches:function(e){return ViewJSON.regex.test(_utils.MessageUtils.getContentType(e))}},render:function(){var e=this.props.content;try{e=JSON.stringify(JSON.parse(e),null,2)}catch(t){}return _react2["default"].createElement("pre",null,e)}}),ViewAuto=_react2["default"].createClass({displayName:"ViewAuto",propTypes:{message:_react2["default"].PropTypes.object.isRequired,flow:_react2["default"].PropTypes.object.isRequired},statics:{matches:function(){return!1},findView:function(e){for(var t=0;t<all.length;t++)if(all[t].matches(e))return all[t];return all[all.length-1]}},render:function(){var e=this.props,t=e.message,a=e.flow,s=ViewAuto.findView(this.props.message);return s.textView?_react2["default"].createElement(ContentLoader,{message:t,flow:a},_react2["default"].createElement(s,{content:""})):_react2["default"].createElement(s,{message:t,flow:a})}}),all=[ViewAuto,ViewImage,ViewJSON,ViewRaw],ContentEmpty=_react2["default"].createClass({displayName:"ContentEmpty",render:function(){var e=this.props.flow.request===this.props.message?"request":"response";return _react2["default"].createElement("div",{className:"alert alert-info"},"No ",e," content.")}}),ContentMissing=_react2["default"].createClass({displayName:"ContentMissing",render:function(){var e=this.props.flow.request===this.props.message?"Request":"Response";return _react2["default"].createElement("div",{className:"alert alert-info"},e," content missing.")}}),TooLarge=_react2["default"].createClass({displayName:"TooLarge",statics:{isTooLarge:function(e){var t=ViewImage.matches(e)?10:.2;return e.contentLength>1048576*t}},render:function(){var e=(0,_utils2.formatSize)(this.props.message.contentLength);return _react2["default"].createElement("div",{className:"alert alert-warning"},_react2["default"].createElement("button",{onClick:this.props.onClick,className:"btn btn-xs btn-warning pull-right"},"Display anyway"),e," content size.")}}),ViewSelector=_react2["default"].createClass({displayName:"ViewSelector",render:function(){for(var e=[],t=0;t<all.length;t++){var a=all[t],s="btn btn-default";a===this.props.active&&(s+=" active");var r;r=a===ViewAuto?"auto: "+ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view",""):a.displayName.toLowerCase().replace("view",""),e.push(_react2["default"].createElement("button",{key:a.displayName,onClick:this.props.selectView.bind(null,a),className:s},r))}return _react2["default"].createElement("div",{className:"view-selector btn-group btn-group-xs"},e)}}),ContentView=_react2["default"].createClass({displayName:"ContentView",getInitialState:function(){return{displayLarge:!1,View:ViewAuto}},propTypes:{flow:_react2["default"].PropTypes.object.isRequired,message:_react2["default"].PropTypes.object.isRequired},selectView:function(e){this.setState({View:e})},displayLarge:function(){this.setState({displayLarge:!0})},componentWillReceiveProps:function(e){e.message!==this.props.message&&this.setState(this.getInitialState())},render:function(){var e=this.props,t=e.flow,a=e.message,a=this.props.message;if(0===a.contentLength)return _react2["default"].createElement(ContentEmpty,this.props);if(null===a.contentLength)return _react2["default"].createElement(ContentMissing,this.props);if(!this.state.displayLarge&&TooLarge.isTooLarge(a))return _react2["default"].createElement(TooLarge,_extends({},this.props,{onClick:this.displayLarge}));var s=_utils.MessageUtils.getContentURL(this.props.flow,a);return _react2["default"].createElement("div",null,this.state.View.textView?_react2["default"].createElement(ContentLoader,{flow:t,message:a},_react2["default"].createElement(this.state.View,{content:""})):_react2["default"].createElement(this.state.View,{flow:t,message:a}),_react2["default"].createElement("div",{className:"view-options text-center"},_react2["default"].createElement(ViewSelector,{selectView:this.selectView,active:this.state.View,message:a})," ",_react2["default"].createElement("a",{className:"btn btn-default btn-xs",href:s},_react2["default"].createElement("i",{className:"fa fa-download"}))))}});exports["default"]=ContentView; +},{"../../utils.js":52,"react":"react","react-dom":"react-dom"}],34:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_EditorBase=require("./EditorBase"),_EditorBase2=_interopRequireDefault(_EditorBase),ValidateEditor=function(e){function t(e){_classCallCheck(this,t);var r=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return r.state={currentContent:e.content},r.onInput=r.onInput.bind(r),r.onDone=r.onDone.bind(r),r}return _inherits(t,e),_createClass(t,[{key:"componentWillReceiveProps",value:function(e){this.setState({currentContent:e.content})}},{key:"onInput",value:function(e){this.setState({currentContent:e}),this.props.onInput&&this.props.onInput(e)}},{key:"onDone",value:function(e){this.props.isValid&&!this.props.isValid(e)&&(this.refs.editor.reset(),e=this.props.content),this.props.onDone(e)}},{key:"render",value:function(){var e=this.props.className||"";return this.props.isValid&&(e+=this.props.isValid(this.state.currentContent)?" has-success":" has-warning"),_react2["default"].createElement(_EditorBase2["default"],_extends({},this.props,{ref:"editor",className:e,onDone:this.onDone,onInput:this.onInput}))}}]),t}(_react.Component);ValidateEditor.propTypes={content:_react.PropTypes.string.isRequired,onDone:_react.PropTypes.func.isRequired,onInput:_react.PropTypes.func,isValid:_react.PropTypes.func,className:_react.PropTypes.string},exports["default"]=ValidateEditor; -},{"../../flow/utils.js":40,"../../utils.js":42,"lodash":"lodash","react":"react"}],24:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_utils=require("../../utils.js"),TimeStamp=_react2["default"].createClass({displayName:"TimeStamp",render:function(){if(!this.props.t)return _react2["default"].createElement("tr",null);var e,t=(0,_utils.formatTimeStamp)(this.props.t);return this.props.deltaTo?(e=(0,_utils.formatTimeDelta)(1e3*(this.props.t-this.props.deltaTo)),e=_react2["default"].createElement("span",{className:"text-muted"},"("+e+")")):e=null,_react2["default"].createElement("tr",null,_react2["default"].createElement("td",null,this.props.title+":"),_react2["default"].createElement("td",null,t," ",e))}}),ConnectionInfo=_react2["default"].createClass({displayName:"ConnectionInfo",render:function(){var e=this.props.conn,t=e.address.address.join(":"),a=_react2["default"].createElement("tr",{key:"sni"});return e.sni&&(a=_react2["default"].createElement("tr",{key:"sni"},_react2["default"].createElement("td",null,_react2["default"].createElement("abbr",{title:"TLS Server Name Indication"},"TLS SNI:")),_react2["default"].createElement("td",null,e.sni))),_react2["default"].createElement("table",{className:"connection-table"},_react2["default"].createElement("tbody",null,_react2["default"].createElement("tr",{key:"address"},_react2["default"].createElement("td",null,"Address:"),_react2["default"].createElement("td",null,t)),a))}}),CertificateInfo=_react2["default"].createClass({displayName:"CertificateInfo",render:function(){var e=this.props.flow,t=e.client_conn,a=e.server_conn,r={maxHeight:100};return _react2["default"].createElement("div",null,t.cert?_react2["default"].createElement("h4",null,"Client Certificate"):null,t.cert?_react2["default"].createElement("pre",{style:r},t.cert):null,a.cert?_react2["default"].createElement("h4",null,"Server Certificate"):null,a.cert?_react2["default"].createElement("pre",{style:r},a.cert):null)}}),Timing=_react2["default"].createClass({displayName:"Timing",render:function(){var e=this.props.flow,t=e.server_conn,a=e.client_conn,r=e.request,l=e.response,n=[{title:"Server conn. initiated",t:t.timestamp_start,deltaTo:r.timestamp_start},{title:"Server conn. TCP handshake",t:t.timestamp_tcp_setup,deltaTo:r.timestamp_start},{title:"Server conn. SSL handshake",t:t.timestamp_ssl_setup,deltaTo:r.timestamp_start},{title:"Client conn. established",t:a.timestamp_start,deltaTo:r.timestamp_start},{title:"Client conn. SSL handshake",t:a.timestamp_ssl_setup,deltaTo:r.timestamp_start},{title:"First request byte",t:r.timestamp_start},{title:"Request complete",t:r.timestamp_end,deltaTo:r.timestamp_start}];e.response&&n.push({title:"First response byte",t:l.timestamp_start,deltaTo:r.timestamp_start},{title:"Response complete",t:l.timestamp_end,deltaTo:r.timestamp_start}),n.forEach(function(e){e.key=e.title}),n=_lodash2["default"].sortBy(n,"t");var s=n.map(function(e){return _react2["default"].createElement(TimeStamp,e)});return _react2["default"].createElement("div",null,_react2["default"].createElement("h4",null,"Timing"),_react2["default"].createElement("table",{className:"timing-table"},_react2["default"].createElement("tbody",null,s)))}}),Details=_react2["default"].createClass({displayName:"Details",render:function(){var e=this.props.flow,t=e.client_conn,a=e.server_conn;return _react2["default"].createElement("section",null,_react2["default"].createElement("h4",null,"Client Connection"),_react2["default"].createElement(ConnectionInfo,{conn:t}),_react2["default"].createElement("h4",null,"Server Connection"),_react2["default"].createElement(ConnectionInfo,{conn:a}),_react2["default"].createElement(CertificateInfo,{flow:e}),_react2["default"].createElement(Timing,{flow:e}))}});exports["default"]=Details; +},{"./EditorBase":33,"react":"react","react-dom":"react-dom"}],35:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function Button(e){var t=e.onClick,r=e.text,a=e.icon;return _react2["default"].createElement("div",{className:"btn btn-default",onClick:t},_react2["default"].createElement("i",{className:"fa fa-fw "+a})," ",r)}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=Button;var _react=require("react"),_react2=_interopRequireDefault(_react);Button.propTypes={onClick:_react.PropTypes.func.isRequired,text:_react.PropTypes.string.isRequired}; -},{"../../utils.js":42,"lodash":"lodash","react":"react"}],25:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_nav=require("./nav.js"),_nav2=_interopRequireDefault(_nav),_messages=require("./messages.js"),_details=require("./details.js"),_details2=_interopRequireDefault(_details),_prompt=require("../prompt.js"),_prompt2=_interopRequireDefault(_prompt),allTabs={request:_messages.Request,response:_messages.Response,error:_messages.Error,details:_details2["default"]},FlowView=_react2["default"].createClass({displayName:"FlowView",getInitialState:function(){return{prompt:!1}},getTabs:function(e){var t=[];return["request","response","error"].forEach(function(r){e[r]&&t.push(r)}),t.push("details"),t},nextTab:function(e){var t=this.getTabs(this.props.flow),r=t.indexOf(this.props.tab),s=(r+e+t.length)%t.length;this.selectTab(t[s])},selectTab:function(e){this.props.updateLocation("/flows/"+this.props.flow.id+"/"+e)},promptEdit:function(){var e;switch(this.props.tab){case"request":e=["method","url",{text:"http version",key:"v"},"header"];break;case"response":e=[{text:"http version",key:"v"},"code","message","header"];break;case"details":return;default:throw"Unknown tab for edit: "+this.props.tab}this.setState({prompt:{done:function(e){this.setState({prompt:!1}),e&&this.refs.tab.edit(e)}.bind(this),options:e}})},render:function(){var e=this.props.flow,t=this.getTabs(e),r=this.props.tab;t.indexOf(r)<0&&(r="response"===r&&e.error?"error":"error"===r&&e.response?"response":t[0]);var s=null;this.state.prompt&&(s=_react2["default"].createElement(_prompt2["default"],this.state.prompt));var a=allTabs[r];return _react2["default"].createElement("div",{className:"flow-detail",onScroll:this.adjustHead},_react2["default"].createElement(_nav2["default"],{ref:"head",flow:e,tabs:t,active:r,selectTab:this.selectTab}),_react2["default"].createElement(a,{ref:"tab",flow:e}),s)}});exports["default"]=FlowView; +},{"react":"react"}],36:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),Splitter=function(e){function t(e,n){_classCallCheck(this,t);var o=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e,n));return o.state={applied:!1,startX:!1,startY:!1},o.onMouseMove=o.onMouseMove.bind(o),o.onMouseUp=o.onMouseUp.bind(o),o.onDragEnd=o.onDragEnd.bind(o),o}return _inherits(t,e),_createClass(t,[{key:"onMouseDown",value:function(e){this.setState({startX:e.pageX,startY:e.pageY}),window.addEventListener("mousemove",this.onMouseMove),window.addEventListener("mouseup",this.onMouseUp),window.addEventListener("dragend",this.onDragEnd)}},{key:"onDragEnd",value:function(){_reactDom2["default"].findDOMNode(this).style.transform="",window.removeEventListener("dragend",this.onDragEnd),window.removeEventListener("mouseup",this.onMouseUp),window.removeEventListener("mousemove",this.onMouseMove)}},{key:"onMouseUp",value:function(e){this.onDragEnd();var t=_reactDom2["default"].findDOMNode(this),n=t.previousElementSibling,o=n.offsetHeight+e.pageY-this.state.startY;"x"===this.props.axis&&(o=n.offsetWidth+e.pageX-this.state.startX),n.style.flex="0 0 "+Math.max(0,o)+"px",t.nextElementSibling.style.flex="1 1 auto",this.setState({applied:!0}),this.onResize()}},{key:"onMouseMove",value:function(e){var t=0,n=0;"x"===this.props.axis?t=e.pageX-this.state.startX:n=e.pageY-this.state.startY,_reactDom2["default"].findDOMNode(this).style.transform="translate("+t+"px, "+n+"px)"}},{key:"onResize",value:function(){window.setTimeout(function(){return window.dispatchEvent(new CustomEvent("resize"))},1)}},{key:"reset",value:function(e){if(this.state.applied){var t=_reactDom2["default"].findDOMNode(this);t.previousElementSibling.style.flex="",t.nextElementSibling.style.flex="",e||this.setState({applied:!1}),this.onResize()}}},{key:"componentWillUnmount",value:function(){this.reset(!0)}},{key:"render",value:function(){return _react2["default"].createElement("div",{className:(0,_classnames2["default"])("splitter","x"===this.props.axis?"splitter-x":"splitter-y")},_react2["default"].createElement("div",{onMouseDown:this.onMouseDown,draggable:"true"}))}}]),t}(_react.Component);Splitter.defaultProps={axis:"x"},exports["default"]=Splitter; -},{"../prompt.js":30,"./details.js":24,"./messages.js":26,"./nav.js":27,"react":"react"}],26:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.Error=exports.Response=exports.Request=void 0;var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var s in r)Object.prototype.hasOwnProperty.call(r,s)&&(e[s]=r[s])}return e},_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_actions=require("../../actions.js"),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js"),_contentview=require("./contentview.js"),_contentview2=_interopRequireDefault(_contentview),_editor=require("../editor.js"),Headers=_react2["default"].createClass({displayName:"Headers",propTypes:{onChange:_react2["default"].PropTypes.func.isRequired,message:_react2["default"].PropTypes.object.isRequired},onChange:function(e,t,r){var s=_lodash2["default"].cloneDeep(this.props.message.headers);s[e][t]=r,s[e][0]||s[e][1]||(1===s.length?(s[0][0]="Name",s[0][1]="Value"):(s.splice(e,1),e===s.length&&(this._nextSel=e-1+"-value"))),this.props.onChange(s)},edit:function(){this.refs["0-key"].focus()},onTab:function(e,t,r){var s=this.props.message.headers;if(e===s.length-1&&1===t){r.preventDefault();var n=_lodash2["default"].cloneDeep(this.props.message.headers);n.push(["Name","Value"]),this.props.onChange(n),this._nextSel=e+1+"-key"}},componentDidUpdate:function(){this._nextSel&&this.refs[this._nextSel]&&(this.refs[this._nextSel].focus(),this._nextSel=void 0)},onRemove:function(e,t,r){1===t?(r.preventDefault(),this.refs[e+"-key"].focus()):e>0&&(r.preventDefault(),this.refs[e-1+"-value"].focus())},render:function(){var e=this.props.message.headers.map(function(e,t){var r=_react2["default"].createElement(HeaderEditor,{ref:t+"-key",content:e[0],onDone:this.onChange.bind(null,t,0),onRemove:this.onRemove.bind(null,t,0),onTab:this.onTab.bind(null,t,0)}),s=_react2["default"].createElement(HeaderEditor,{ref:t+"-value",content:e[1],onDone:this.onChange.bind(null,t,1),onRemove:this.onRemove.bind(null,t,1),onTab:this.onTab.bind(null,t,1)});return _react2["default"].createElement("tr",{key:t},_react2["default"].createElement("td",{className:"header-name"},r,":"),_react2["default"].createElement("td",{className:"header-value"},s))}.bind(this));return _react2["default"].createElement("table",{className:"header-table"},_react2["default"].createElement("tbody",null,e))}}),HeaderEditor=_react2["default"].createClass({displayName:"HeaderEditor",render:function(){return _react2["default"].createElement(_editor.ValueEditor,_extends({ref:"input"},this.props,{onKeyDown:this.onKeyDown,inline:!0}))},focus:function(){_reactDom2["default"].findDOMNode(this).focus()},onKeyDown:function(e){switch(e.keyCode){case _utils2.Key.BACKSPACE:var t=window.getSelection().getRangeAt(0);0===t.startOffset&&0===t.endOffset&&this.props.onRemove(e);break;case _utils2.Key.TAB:e.shiftKey||this.props.onTab(e)}}}),RequestLine=_react2["default"].createClass({displayName:"RequestLine",render:function(){var e=this.props.flow,t=_utils.RequestUtils.pretty_url(e.request),r=e.request.http_version;return _react2["default"].createElement("div",{className:"first-line request-line"},_react2["default"].createElement(_editor.ValueEditor,{ref:"method",content:e.request.method,onDone:this.onMethodChange,inline:!0})," ",_react2["default"].createElement(_editor.ValueEditor,{ref:"url",content:t,onDone:this.onUrlChange,isValid:this.isValidUrl,inline:!0})," ",_react2["default"].createElement(_editor.ValueEditor,{ref:"httpVersion",content:r,onDone:this.onHttpVersionChange,isValid:_utils.isValidHttpVersion,inline:!0}))},isValidUrl:function(e){var t=(0,_utils.parseUrl)(e);return!!t.host},onMethodChange:function(e){_actions.FlowActions.update(this.props.flow,{request:{method:e}})},onUrlChange:function(e){var t=(0,_utils.parseUrl)(e);t.path=t.path||"",_actions.FlowActions.update(this.props.flow,{request:t})},onHttpVersionChange:function(e){var t=(0,_utils.parseHttpVersion)(e);_actions.FlowActions.update(this.props.flow,{request:{http_version:t}})}}),ResponseLine=_react2["default"].createClass({displayName:"ResponseLine",render:function(){var e=this.props.flow,t=e.response.http_version;return _react2["default"].createElement("div",{className:"first-line response-line"},_react2["default"].createElement(_editor.ValueEditor,{ref:"httpVersion",content:t,onDone:this.onHttpVersionChange,isValid:_utils.isValidHttpVersion,inline:!0})," ",_react2["default"].createElement(_editor.ValueEditor,{ref:"code",content:e.response.status_code+"",onDone:this.onCodeChange,isValid:this.isValidCode,inline:!0})," ",_react2["default"].createElement(_editor.ValueEditor,{ref:"msg",content:e.response.reason,onDone:this.onMsgChange,inline:!0}))},isValidCode:function(e){return/^\d+$/.test(e)},onHttpVersionChange:function(e){var t=(0,_utils.parseHttpVersion)(e);_actions.FlowActions.update(this.props.flow,{response:{http_version:t}})},onMsgChange:function(e){_actions.FlowActions.update(this.props.flow,{response:{msg:e}})},onCodeChange:function(e){e=parseInt(e),_actions.FlowActions.update(this.props.flow,{response:{code:e}})}}),Request=exports.Request=_react2["default"].createClass({displayName:"Request",render:function(){var e=this.props.flow;return _react2["default"].createElement("section",{className:"request"},_react2["default"].createElement(RequestLine,{ref:"requestLine",flow:e}),_react2["default"].createElement(Headers,{ref:"headers",message:e.request,onChange:this.onHeaderChange}),_react2["default"].createElement("hr",null),_react2["default"].createElement(_contentview2["default"],{flow:e,message:e.request}))},edit:function(e){switch(e){case"m":this.refs.requestLine.refs.method.focus();break;case"u":this.refs.requestLine.refs.url.focus();break;case"v":this.refs.requestLine.refs.httpVersion.focus();break;case"h":this.refs.headers.edit();break;default:throw"Unimplemented: "+e}},onHeaderChange:function(e){_actions.FlowActions.update(this.props.flow,{request:{headers:e}})}}),Response=exports.Response=_react2["default"].createClass({displayName:"Response",render:function(){var e=this.props.flow;return _react2["default"].createElement("section",{className:"response"},_react2["default"].createElement(ResponseLine,{ref:"responseLine",flow:e}),_react2["default"].createElement(Headers,{ref:"headers",message:e.response,onChange:this.onHeaderChange}),_react2["default"].createElement("hr",null),_react2["default"].createElement(_contentview2["default"],{flow:e,message:e.response}))},edit:function(e){switch(e){case"c":this.refs.responseLine.refs.status_code.focus();break;case"m":this.refs.responseLine.refs.msg.focus();break;case"v":this.refs.responseLine.refs.httpVersion.focus();break;case"h":this.refs.headers.edit();break;default:throw"Unimplemented: "+e}},onHeaderChange:function(e){_actions.FlowActions.update(this.props.flow,{response:{headers:e}})}}),Error=exports.Error=_react2["default"].createClass({displayName:"Error",render:function(){var e=this.props.flow;return _react2["default"].createElement("section",null,_react2["default"].createElement("div",{className:"alert alert-warning"},e.error.msg,_react2["default"].createElement("div",null,_react2["default"].createElement("small",null,(0,_utils2.formatTimeStamp)(e.error.timestamp)))))}}); +},{"classnames":"classnames","react":"react","react-dom":"react-dom"}],37:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function ToggleButton(e){var t=e.checked,r=e.onToggle,a=e.text;return _react2["default"].createElement("div",{className:"btn btn-toggle "+(t?"btn-primary":"btn-default"),onClick:r},_react2["default"].createElement("i",{className:"fa fa-fw "+(t?"fa-check-square-o":"fa-square-o")})," ",a)}Object.defineProperty(exports,"__esModule",{value:!0}),exports["default"]=ToggleButton;var _react=require("react"),_react2=_interopRequireDefault(_react);ToggleButton.propTypes={checked:_react.PropTypes.bool.isRequired,onToggle:_react.PropTypes.func.isRequired,text:_react.PropTypes.string.isRequired}; -},{"../../actions.js":2,"../../flow/utils.js":40,"../../utils.js":42,"../editor.js":22,"./contentview.js":23,"lodash":"lodash","react":"react","react-dom":"react-dom"}],27:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_actions=require("../../actions.js"),NavAction=_react2["default"].createClass({displayName:"NavAction",onClick:function(e){e.preventDefault(),this.props.onClick()},render:function(){return _react2["default"].createElement("a",{title:this.props.title,href:"#",className:"nav-action",onClick:this.onClick},_react2["default"].createElement("i",{className:"fa fa-fw "+this.props.icon}))}}),Nav=_react2["default"].createClass({displayName:"Nav",render:function(){var e=this.props.flow,t=this.props.tabs.map(function(e){var t=e.charAt(0).toUpperCase()+e.slice(1),a=this.props.active===e?"active":"",c=function(t){this.props.selectTab(e),t.preventDefault()}.bind(this);return _react2["default"].createElement("a",{key:e,href:"#",className:a,onClick:c},t)}.bind(this)),a=null;e.intercepted&&(a=_react2["default"].createElement(NavAction,{title:"[a]ccept intercepted flow",icon:"fa-play",onClick:_actions.FlowActions.accept.bind(null,e)}));var c=null;return e.modified&&(c=_react2["default"].createElement(NavAction,{title:"revert changes to flow [V]",icon:"fa-history",onClick:_actions.FlowActions.revert.bind(null,e)})),_react2["default"].createElement("nav",{ref:"head",className:"nav-tabs nav-tabs-sm"},t,_react2["default"].createElement(NavAction,{title:"[d]elete flow",icon:"fa-trash",onClick:_actions.FlowActions["delete"].bind(null,e)}),_react2["default"].createElement(NavAction,{title:"[D]uplicate flow",icon:"fa-copy",onClick:_actions.FlowActions.duplicate.bind(null,e)}),_react2["default"].createElement(NavAction,{disabled:!0,title:"[r]eplay flow",icon:"fa-repeat",onClick:_actions.FlowActions.replay.bind(null,e)}),a,c)}});exports["default"]=Nav; +},{"react":"react"}],38:[function(require,module,exports){ +"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_utils=require("../../utils"),ToggleInputButton=function(e){function t(e){_classCallCheck(this,t);var n=_possibleConstructorReturn(this,Object.getPrototypeOf(t).call(this,e));return n.state={txt:e.txt},n}return _inherits(t,e),_createClass(t,[{key:"onChange",value:function(e){this.setState({txt:e.target.value})}},{key:"onKeyDown",value:function(e){e.stopPropagation(),e.keyCode===_utils.Key.ENTER&&this.props.onToggleChanged(this.state.txt)}},{key:"render",value:function(){var e=this;return _react2["default"].createElement("div",{className:"input-group toggle-input-btn"},_react2["default"].createElement("span",{className:"input-group-btn",onClick:function(){return e.props.onToggleChanged(e.state.txt)}},_react2["default"].createElement("div",{className:(0,_classnames2["default"])("btn",this.props.checked?"btn-primary":"btn-default")},_react2["default"].createElement("span",{className:(0,_classnames2["default"])("fa",this.props.checked?"fa-check-square-o":"fa-square-o")})," ",this.props.name)),_react2["default"].createElement("input",{className:"form-control",placeholder:this.props.placeholder,disabled:this.props.checked,value:this.state.txt,type:this.props.inputType,onChange:function(t){return e.onChange(t)},onKeyDown:function(t){return e.onKeyDown(t)}}))}}]),t}(_react.Component);ToggleInputButton.propTypes={name:_react.PropTypes.string.isRequired,txt:_react.PropTypes.string.isRequired,onToggleChanged:_react.PropTypes.func.isRequired},exports["default"]=ToggleInputButton; -},{"../../actions.js":2,"react":"react"}],28:[function(require,module,exports){ +},{"../../utils":52,"classnames":"classnames","react":"react"}],39:[function(require,module,exports){ "use strict";function _interopRequireDefault(t){return t&&t.__esModule?t:{"default":t}}function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function _inherits(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function t(t,e){for(var o=0;o<e.length;o++){var r=e[o];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,o,r){return o&&t(e.prototype,o),r&&t(e,r),e}}(),_get=function t(e,o,r){null===e&&(e=Function.prototype);var n=Object.getOwnPropertyDescriptor(e,o);if(void 0===n){var i=Object.getPrototypeOf(e);return null===i?void 0:t(i,o,r)}if("value"in n)return n.value;var c=n.get;if(void 0!==c)return c.call(r)},_react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),symShouldStick=Symbol("shouldStick"),isAtBottom=function(t){return t.scrollTop+t.clientHeight===t.scrollHeight};exports["default"]=function(t){var e,o;return Object.assign((o=e=function(t){function e(){return _classCallCheck(this,e),_possibleConstructorReturn(this,Object.getPrototypeOf(e).apply(this,arguments))}return _inherits(e,t),_createClass(e,[{key:"componentWillUpdate",value:function(){var t=_reactDom2["default"].findDOMNode(this);this[symShouldStick]=t.scrollTop&&isAtBottom(t),_get(Object.getPrototypeOf(e.prototype),"componentWillUpdate",this)&&_get(Object.getPrototypeOf(e.prototype),"componentWillUpdate",this).call(this)}},{key:"componentDidUpdate",value:function(){var t=_reactDom2["default"].findDOMNode(this);this[symShouldStick]&&!isAtBottom(t)&&(t.scrollTop=t.scrollHeight),_get(Object.getPrototypeOf(e.prototype),"componentDidUpdate",this)&&_get(Object.getPrototypeOf(e.prototype),"componentDidUpdate",this).call(this)}}]),e}(t),e.displayName=t.name,o),t)}; -},{"react":"react","react-dom":"react-dom"}],29:[function(require,module,exports){ +},{"react":"react","react-dom":"react-dom"}],40:[function(require,module,exports){ "use strict";function calcVScroll(t){if(!t)return{start:0,end:0,paddingTop:0,paddingBottom:0};var e=t.itemCount,o=t.rowHeight,r=t.viewportTop,a=t.viewportHeight,i=t.itemHeights,l=r+a,n=0,c=0,d=0,p=0;if(i)for(var h=0,s=0;e>h;h++){var m=i[h]||o;r>=s&&h%2===0&&(d=s,n=h),l>=s?c=h+1:p+=m,s+=m}else n=-2&Math.max(0,Math.floor(r/o)-1),c=Math.min(e,n+Math.ceil(a/o)+2),d=Math.min(n,e)*o,p=Math.max(0,e-c)*o;return{start:n,end:c,paddingTop:d,paddingBottom:p}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.calcVScroll=calcVScroll; -},{}],30:[function(require,module,exports){ -"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_utils=require("../utils.js"),Prompt=_react2["default"].createClass({displayName:"Prompt",contextTypes:{returnFocus:_react2["default"].PropTypes.func},propTypes:{options:_react2["default"].PropTypes.array.isRequired,done:_react2["default"].PropTypes.func.isRequired,prompt:_react2["default"].PropTypes.string},componentDidMount:function(){_reactDom2["default"].findDOMNode(this).focus()},onKeyDown:function(e){e.stopPropagation(),e.preventDefault();for(var t=this.getOptions(),r=0;r<t.length;r++){var o=t[r].key;if(_utils.Key[o.toUpperCase()]===e.keyCode)return void this.done(o)}e.keyCode!==_utils.Key.ESC&&e.keyCode!==_utils.Key.ENTER||this.done(!1)},onClick:function(e){this.done(!1)},done:function(e){this.props.done(e),this.context.returnFocus()},getOptions:function(){for(var e=[],t=function(t){return _lodash2["default"].includes(_lodash2["default"].map(e,"key"),t)},r=0;r<this.props.options.length;r++){var o=this.props.options[r];if(_lodash2["default"].isString(o)){for(var n=o;n.length>0&&t(n[0]);)n=n.substr(1);o={text:o,key:n[0]}}if(!o.text||!o.key||t(o.key))throw"invalid options";e.push(o)}return e},render:function(){var e=this.getOptions();return e=_lodash2["default"].map(e,function(e){var t,r,o=e.text.indexOf(e.key);-1!==o?(t=e.text.substring(0,o),r=e.text.substring(o+1)):(t=e.text+" (",r=")");var n=function(t){this.done(e.key),t.stopPropagation()}.bind(this);return _react2["default"].createElement("span",{key:e.key,className:"option",onClick:n},t,_react2["default"].createElement("strong",{className:"text-primary"},e.key),r)}.bind(this)),_react2["default"].createElement("div",{tabIndex:"0",onKeyDown:this.onKeyDown,onClick:this.onClick,className:"prompt-dialog"},_react2["default"].createElement("div",{className:"prompt-content"},this.props.prompt||_react2["default"].createElement("strong",null,"Select: "),e))}});exports["default"]=Prompt; - -},{"../utils.js":42,"lodash":"lodash","react":"react","react-dom":"react-dom"}],31:[function(require,module,exports){ +},{}],41:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.AppDispatcher=void 0;var _flux=require("flux"),_flux2=_interopRequireDefault(_flux),PayloadSources={VIEW:"view",SERVER:"server"},AppDispatcher=exports.AppDispatcher=new _flux2["default"].Dispatcher;AppDispatcher.dispatchViewAction=function(e){e.source=PayloadSources.VIEW,this.dispatch(e)},AppDispatcher.dispatchServerAction=function(e){e.source=PayloadSources.SERVER,this.dispatch(e)}; -},{"flux":"flux"}],32:[function(require,module,exports){ +},{"flux":"flux"}],42:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function _defineProperty(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function reducer(){var e=arguments.length<=0||void 0===arguments[0]?defaultState:arguments[0],t=arguments[1];switch(t.type){case TOGGLE_FILTER:var i=_extends({},e.filter,_defineProperty({},t.filter,!e.filter[t.filter]));return _extends({},e,{filter:i,filteredEvents:(0,_view.updateViewFilter)(e.events,function(e){return i[e.level]})});case TOGGLE_VISIBILITY:return _extends({},e,{visible:!e.visible});case UPDATE_LOG:var r=reduceList(e.events,t);return _extends({},e,{events:r,filteredEvents:(0,_view.updateViewList)(e.filteredEvents,e.events,r,t,function(t){return e.filter[t.level]})});default:return e}}function toggleEventLogFilter(e){return{type:TOGGLE_FILTER,filter:e}}function toggleEventLogVisibility(){return{type:TOGGLE_VISIBILITY}}function addLogEntry(e){var t=arguments.length<=1||void 0===arguments[1]?"web":arguments[1];return addItem({message:e,level:t,id:"log-"+id++})}Object.defineProperty(exports,"__esModule",{value:!0}),exports.fetchLogEntries=exports.updateLogEntries=exports.UPDATE_LOG=void 0;var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var i=arguments[t];for(var r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r])}return e};exports["default"]=reducer,exports.toggleEventLogFilter=toggleEventLogFilter,exports.toggleEventLogVisibility=toggleEventLogVisibility,exports.addLogEntry=addLogEntry;var _list=require("./utils/list"),_list2=_interopRequireDefault(_list),_view=require("./utils/view"),TOGGLE_FILTER="TOGGLE_EVENTLOG_FILTER",TOGGLE_VISIBILITY="TOGGLE_EVENTLOG_VISIBILITY",UPDATE_LOG=exports.UPDATE_LOG="UPDATE_EVENTLOG",_makeList=(0,_list2["default"])(UPDATE_LOG,"/events"),reduceList=_makeList.reduceList,updateList=_makeList.updateList,fetchList=_makeList.fetchList,addItem=_makeList.addItem,defaultState={visible:!1,filter:{debug:!1,info:!0,web:!0},events:reduceList(),filteredEvents:[]},id=0;exports.updateLogEntries=updateList,exports.fetchLogEntries=fetchList; -},{"./utils/list":36,"./utils/view":37}],33:[function(require,module,exports){ +},{"./utils/list":46,"./utils/view":47}],43:[function(require,module,exports){ "use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t}function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function makeFilterFn(e){return e?_filt2["default"].parse(e):function(){return!0}}function makeSortFn(e){var t=columns[e.sortColumn];if(t){var r=t.sortKeyFun;return e.sortDesc&&(r=r&&function(e){var r=t.sortKeyFun(e);return _.isString(r)?(0,_utils.reverseString)(""+r):-r}),r}}function reducer(){var e=arguments.length<=0||void 0===arguments[0]?defaultState:arguments[0],t=arguments[1];switch(t.type){case UPDATE_FLOWS:var r=reduceList(e.all,t);return _extends({},e,{all:r,view:(0,_view.updateViewList)(e.view,e.all,r,t,makeFilterFn(t.filter),makeSortFn(e.sort))});case SET_FILTER:return _extends({},e,{filter:t.filter,view:(0,_view.updateViewFilter)(e.all,makeFilterFn(t.filter),makeSortFn(e.sort))});case SET_HIGHLIGHT:return _extends({},e,{highlight:t.highlight});case SET_SORT:return _extends({},e,{sort:t.sort,view:(0,_view.updateViewSort)(e.view,makeSortFn(t.sort))});case SELECT_FLOW:return _extends({},e,{selected:[t.flowId]});default:return e}}function setFilter(e){return{type:SET_FILTER,filter:e}}function setHighlight(e){return{type:SET_HIGHLIGHT,highlight:e}}function setSort(e){return{type:SET_SORT,sort:e}}function selectFlow(e){return function(t,r){t({type:SELECT_FLOW,currentSelection:r().flows.selected[0],flowId:e})}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.fetchFlows=exports.updateFlows=exports.SELECT_FLOW=exports.SET_SORT=exports.SET_HIGHLIGHT=exports.SET_FILTER=exports.UPDATE_FLOWS=void 0;var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(e[i]=r[i])}return e};exports["default"]=reducer,exports.setFilter=setFilter,exports.setHighlight=setHighlight,exports.setSort=setSort,exports.selectFlow=selectFlow;var _list=require("./utils/list"),_list2=_interopRequireDefault(_list),_filt=require("../filt/filt"),_filt2=_interopRequireDefault(_filt),_view=require("./utils/view"),_utils=require("../utils.js"),_FlowColumns=require("../components/FlowTable/FlowColumns"),columns=_interopRequireWildcard(_FlowColumns),UPDATE_FLOWS=exports.UPDATE_FLOWS="UPDATE_FLOWS",SET_FILTER=exports.SET_FILTER="SET_FLOW_FILTER",SET_HIGHLIGHT=exports.SET_HIGHLIGHT="SET_FLOW_HIGHLIGHT",SET_SORT=exports.SET_SORT="SET_FLOW_SORT",SELECT_FLOW=exports.SELECT_FLOW="SELECT_FLOW",_makeList=(0,_list2["default"])(UPDATE_FLOWS,"/flows"),reduceList=_makeList.reduceList,updateList=_makeList.updateList,fetchList=_makeList.fetchList,defaultState={all:reduceList(),selected:[],view:[],filter:void 0,highlight:void 0,sort:{sortColumn:void 0,sortDesc:!1}};exports.updateFlows=updateList,exports.fetchFlows=fetchList; -},{"../components/FlowTable/FlowColumns":7,"../filt/filt":39,"../utils.js":42,"./utils/list":36,"./utils/view":37}],34:[function(require,module,exports){ +},{"../components/FlowTable/FlowColumns":12,"../filt/filt":49,"../utils.js":52,"./utils/list":46,"./utils/view":47}],44:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0});var _redux=require("redux"),_eventLog=require("./eventLog"),_eventLog2=_interopRequireDefault(_eventLog),_websocket=require("./websocket"),_websocket2=_interopRequireDefault(_websocket),_flows=require("./flows"),_flows2=_interopRequireDefault(_flows),_ui=require("./ui"),_ui2=_interopRequireDefault(_ui),rootReducer=(0,_redux.combineReducers)({eventLog:_eventLog2["default"],websocket:_websocket2["default"],flows:_flows2["default"],ui:_ui2["default"]});exports["default"]=rootReducer; -},{"./eventLog":32,"./flows":33,"./ui":35,"./websocket":38,"redux":"redux"}],35:[function(require,module,exports){ +},{"./eventLog":42,"./flows":43,"./ui":45,"./websocket":48,"redux":"redux"}],45:[function(require,module,exports){ "use strict";function reducer(){var e=arguments.length<=0||void 0===arguments[0]?defaultState:arguments[0],t=arguments[1];switch(t.type){case SET_ACTIVE_MENU:return _extends({},e,{activeMenu:t.activeMenu});case _flows.SELECT_FLOW:var r=t.flowId&&!t.currentSelection,n=!t.flowId&&t.currentSelection;return r?_extends({},e,{activeMenu:"Flow"}):n&&"Flow"===e.activeMenu?_extends({},e,{activeMenu:"Start"}):e;default:return e}}function setActiveMenu(e){return{type:SET_ACTIVE_MENU,activeMenu:e}}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e};exports["default"]=reducer,exports.setActiveMenu=setActiveMenu;var _flows=require("./flows"),SET_ACTIVE_MENU="SET_ACTIVE_MENU",defaultState={activeMenu:"Start"}; -},{"./flows":33}],36:[function(require,module,exports){ +},{"./flows":43}],46:[function(require,module,exports){ "use strict";function _defineProperty(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function _toConsumableArray(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t<e.length;t++)r[t]=e[t];return r}return Array.from(e)}function makeList(e,t){function r(){var t=arguments.length<=0||void 0===arguments[0]?defaultState:arguments[0],n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];if(n.type!==e)return t;if(n.cmd===RECEIVE_LIST){for(var i={isFetching:!1,actionsDuringFetch:[],list:n.list,byId:{},indexOf:{}},o=0;o<n.list.length;o++){var d=n.list[o];i.byId[d.id]=d,i.indexOf[d.id]=o}var a=!0,s=!1,u=void 0;try{for(var c,f=t.actionsDuringFetch[Symbol.iterator]();!(a=(c=f.next()).done);a=!0)n=c.value,i=r(i,n)}catch(l){s=!0,u=l}finally{try{!a&&f["return"]&&f["return"]()}finally{if(s)throw u}}return i}if(t.isFetching)return _extends({},t,{actionsDuringFetch:[].concat(_toConsumableArray(t.actionsDuringFetch),[n])});var E=void 0,y=void 0;switch(n.cmd){case ADD:return{list:[].concat(_toConsumableArray(t.list),[n.item]),byId:_extends({},t.byId,_defineProperty({},n.item.id,n.item)),indexOf:_extends({},t.indexOf,_defineProperty({},n.item.id,t.list.length))};case UPDATE:return E=[].concat(_toConsumableArray(t.list)),y=t.indexOf[n.item.id],E[y]=n.item,_extends({},t,{list:E,byId:_extends({},t.byId,_defineProperty({},n.item.id,n.item))});case REMOVE:return E=[].concat(_toConsumableArray(t.list)),y=t.indexOf[n.item.id],E.splice(y,1),_extends({},t,{list:E,byId:_extends({},t.byId,_defineProperty({},n.item.id,void 0)),indexOf:_extends({},t.indexOf,_defineProperty({},n.item.id,void 0))});case REQUEST_LIST:return _extends({},t,{isFetching:!0});default:return console.debug("unknown action",n),t}}function n(t){return{type:e,cmd:ADD,item:t}}function i(t){return{type:e,cmd:UPDATE,item:t}}function o(t){return{type:e,cmd:REMOVE,item:t}}function d(e){return function(t){switch(e.cmd){case"add":return t(n(e.data));case"update":return t(i(e.data));case"remove":return t(o(e.data));case"reset":return t(u());default:console.error("unknown list update",e)}}}function a(){return{type:e,cmd:REQUEST_LIST}}function s(t){return{type:e,cmd:RECEIVE_LIST,list:t}}function u(){return function(e){return e(a()),(0,_utils.fetchApi)(t).then(function(t){return t.json().then(function(t){e(s(t.data))})})}}return{reduceList:r,updateList:d,fetchList:u,addItem:n,updateItem:i,removeItem:o}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.RECEIVE_LIST=exports.REQUEST_LIST=exports.REMOVE=exports.UPDATE=exports.ADD=void 0;var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e};exports["default"]=makeList;var _utils=require("../../utils"),ADD=exports.ADD="ADD",UPDATE=exports.UPDATE="UPDATE",REMOVE=exports.REMOVE="REMOVE",REQUEST_LIST=exports.REQUEST_LIST="REQUEST_LIST",RECEIVE_LIST=exports.RECEIVE_LIST="RECEIVE_LIST",defaultState={list:[],isFetching:!1,actionsDuringFetch:[],byId:{},indexOf:{}}; -},{"../../utils":42}],37:[function(require,module,exports){ +},{"../../utils":52}],47:[function(require,module,exports){ "use strict";function _toConsumableArray(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t<e.length;t++)r[t]=e[t];return r}return Array.from(e)}function sortedIndexOf(e,t,r){r||(r=function(e){return 0});for(var n=0,o=e.length,i=r(t),u=void 0;o>n;)u=n+o>>>1,r(e[u])<i?n=u+1:o=u;for(;e[n].id!==t.id&&r(e[n+1])===i;)n++;return n}function updateViewList(e,t,r,n){var o=arguments.length<=4||void 0===arguments[4]?defaultFilterFn:arguments[4],i=arguments.length<=5||void 0===arguments[5]?defaultSortFn:arguments[5];switch(n.cmd){case _list.REQUEST_LIST:return e;case _list.RECEIVE_LIST:return updateViewFilter(r,o,i);case _list.ADD:return o(n.item)?sortedInsert(e,i,n.item):e;case _list.UPDATE:var u=t.byId[n.item.id],d=n.item,a=o(u),f=o(d);if(!a&&f)return sortedInsert(e,i,n.item);if(a&&!f)return sortedRemove(e,i,n.item);if(a&&f){var s=function(){var t=[].concat(_toConsumableArray(e));return t.indexOf=function(e){return sortedIndexOf(t,e,i)},t[t.indexOf(u)]=d,i&&i(u)!==i(d)&&t.sort(makeCompareFn(i)),{v:t}}();if("object"===("undefined"==typeof s?"undefined":_typeof(s)))return s.v}return e;case _list.REMOVE:var l=o(t.byId[n.item.id]);return l?sortedRemove(e,i,n.item):e;default:return console.error("Unknown list action: ",n),e}}function updateViewFilter(e){var t=arguments.length<=1||void 0===arguments[1]?defaultFilterFn:arguments[1],r=arguments.length<=2||void 0===arguments[2]?defaultSortFn:arguments[2],n=e.list.filter(t);return r&&n.sort(makeCompareFn(r)),n.indexOf=function(e){return sortedIndexOf(n,e,r)},n}function updateViewSort(e){var t=arguments.length<=1||void 0===arguments[1]?defaultSortFn:arguments[1],r=[].concat(_toConsumableArray(e));return t&&r.sort(makeCompareFn(t)),r.indexOf=function(e){return sortedIndexOf(r,e,t)},r}Object.defineProperty(exports,"__esModule",{value:!0});var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};exports.sortedIndexOf=sortedIndexOf,exports.updateViewList=updateViewList,exports.updateViewFilter=updateViewFilter,exports.updateViewSort=updateViewSort;var _list=require("./list"),defaultFilterFn=function(e){return!0},defaultSortFn=!1,makeCompareFn=function(e){var t=function(t,r){var n=e(t),o=e(r);return o>n?-1:n>o?1:0};return t},sortedInsert=function(e,t,r){var n=[].concat(_toConsumableArray(e),[r]);n.indexOf=function(e){return sortedIndexOf(n,e,t)};var o=makeCompareFn(t);return t&&o(e[e.length-1],r)>0&&(console.debug("sorting view..."),n.sort(o)),n},sortedRemove=function(e,t,r){var n=r.id,o=e.filter(function(e){return e.id!==n});return o.indexOf=function(e){return sortedIndexOf(o,e,t)},o}; -},{"./list":36}],38:[function(require,module,exports){ +},{"./list":46}],48:[function(require,module,exports){ "use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]=e[t]);return n["default"]=e,n}function reducer(){var e=arguments.length<=0||void 0===arguments[0]?defaultState:arguments[0],n=arguments[1];switch(n.type){case CONNECT:return _extends({},e,{socket:n.socket});case CONNECTED:return _extends({},e,{connected:!0});case DISCONNECT:return _extends({},e,{connected:!1});case DISCONNECTED:return _extends({},e,{socket:null});default:return e}}function connect(){return function(e){var n=new WebSocket(location.origin.replace("http","ws")+"/updates");return window.ws=n,n.addEventListener("open",function(){return e(onConnect())}),n.addEventListener("close",function(){return e(onDisconnect())}),n.addEventListener("message",function(n){return e(onMessage(n))}),n.addEventListener("error",function(n){return e(onError(n))}),e({type:CONNECT,socket:n}),n}}function disconnect(){return{type:DISCONNECT}}function onConnect(){return function(e){e({type:CONNECTED}),e(flowActions.fetchFlows()).then(function(){return _actions.ConnectionActions.open()})}}function onDisconnect(){return function(e){_actions.ConnectionActions.close(),e(eventLogActions.addLogEntry("WebSocket connection closed.")),e({type:DISCONNECTED})}}function onMessage(e){return function(n){var t=JSON.parse(e.data);switch(_dispatcher.AppDispatcher.dispatchServerAction(t),t.type){case eventLogActions.UPDATE_LOG:return n(eventLogActions.updateLogEntries(t));case flowActions.UPDATE_FLOWS:return n(flowActions.updateFlows(t));default:console.warn("unknown message",t)}n({type:MESSAGE,msg:e})}}function onError(e){return function(n){_actions.ConnectionActions.error(),n(eventLogActions.addLogEntry("WebSocket connection error.")),n({type:ERROR,error:e})}}Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var t=arguments[n];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o])}return e};exports["default"]=reducer,exports.connect=connect,exports.disconnect=disconnect,exports.onConnect=onConnect,exports.onDisconnect=onDisconnect,exports.onMessage=onMessage,exports.onError=onError;var _actions=require("../actions.js"),_dispatcher=require("../dispatcher.js"),_eventLog=require("./eventLog"),eventLogActions=_interopRequireWildcard(_eventLog),_flows=require("./flows"),flowActions=_interopRequireWildcard(_flows),CONNECT="WEBSOCKET_CONNECT",CONNECTED="WEBSOCKET_CONNECTED",DISCONNECT="WEBSOCKET_DISCONNECT",DISCONNECTED="WEBSOCKET_DISCONNECTED",ERROR="WEBSOCKET_ERROR",MESSAGE="WEBSOCKET_MESSAGE",defaultState={connected:!1,socket:null}; -},{"../actions.js":2,"../dispatcher.js":31,"./eventLog":32,"./flows":33}],39:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":41,"./eventLog":42,"./flows":43}],49:[function(require,module,exports){ "use strict";module.exports=function(){function e(e,t){function r(){this.constructor=e}r.prototype=t.prototype,e.prototype=new r}function t(e,r,n,i){this.message=e,this.expected=r,this.found=n,this.location=i,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,t)}function r(e){function r(t){var r,n,i=Ot[t];if(i)return i;for(r=t-1;!Ot[r];)r--;for(i=Ot[r],i={line:i.line,column:i.column,seenCR:i.seenCR};t>r;)n=e.charAt(r),"\n"===n?(i.seenCR||i.line++,i.column=1,i.seenCR=!1):"\r"===n||"\u2028"===n||"\u2029"===n?(i.line++,i.column=1,i.seenCR=!0):(i.column++,i.seenCR=!1),r++;return Ot[t]=i,i}function n(e,t){var n=r(e),i=r(t);return{start:{offset:e,line:n.line,column:n.column},end:{offset:t,line:i.line,column:i.column}}}function i(e){Pt>Mt||(Mt>Pt&&(Pt=Mt,Qt=[]),Qt.push(e))}function s(e,r,n,i){function s(e){var t=1;for(e.sort(function(e,t){return e.description<t.description?-1:e.description>t.description?1:0});t<e.length;)e[t-1]===e[t]?e.splice(t,1):t++}function u(e,t){function r(e){function t(e){return e.charCodeAt(0).toString(16).toUpperCase()}return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E\x0F]/g,function(e){return"\\x0"+t(e)}).replace(/[\x10-\x1F\x80-\xFF]/g,function(e){return"\\x"+t(e)}).replace(/[\u0100-\u0FFF]/g,function(e){return"\\u0"+t(e)}).replace(/[\u1000-\uFFFF]/g,function(e){return"\\u"+t(e)})}var n,i,s,u=new Array(e.length);for(s=0;s<e.length;s++)u[s]=e[s].description;return n=e.length>1?u.slice(0,-1).join(", ")+" or "+u[e.length-1]:u[0],i=t?'"'+r(t)+'"':"end of input","Expected "+n+" but "+i+" found."}return null!==r&&s(r),new t(null!==e?e:u(r,n),r,n,i)}function u(){var e,t,r,n;return Vt++,e=Mt,t=a(),t!==P?(r=l(),r!==P?(n=a(),n!==P?(Nt=e,t=X(r),e=t):(Mt=e,e=P)):(Mt=e,e=P)):(Mt=e,e=P),Vt--,e===P&&(t=P,0===Vt&&i(W)),e}function c(){var t,r;return Vt++,Z.test(e.charAt(Mt))?(t=e.charAt(Mt),Mt++):(t=P,0===Vt&&i($)),Vt--,t===P&&(r=P,0===Vt&&i(Y)),t}function o(){var t,r;return Vt++,te.test(e.charAt(Mt))?(t=e.charAt(Mt),Mt++):(t=P,0===Vt&&i(re)),Vt--,t===P&&(r=P,0===Vt&&i(ee)),t}function a(){var e,t;for(Vt++,e=[],t=c();t!==P;)e.push(t),t=c();return Vt--,e===P&&(t=P,0===Vt&&i(ne)),e}function l(){var t,r,n,s,u,c;return t=Mt,r=p(),r!==P?(n=a(),n!==P?(124===e.charCodeAt(Mt)?(s=ie,Mt++):(s=P,0===Vt&&i(se)),s!==P?(u=a(),u!==P?(c=l(),c!==P?(Nt=t,r=ue(r,c),t=r):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P),t===P&&(t=p()),t}function p(){var t,r,n,s,u,o;if(t=Mt,r=f(),r!==P?(n=a(),n!==P?(38===e.charCodeAt(Mt)?(s=ce,Mt++):(s=P,0===Vt&&i(oe)),s!==P?(u=a(),u!==P?(o=p(),o!==P?(Nt=t,r=ae(r,o),t=r):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P),t===P){if(t=Mt,r=f(),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=p(),s!==P?(Nt=t,r=ae(r,s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;t===P&&(t=f())}return t}function f(){var t,r,n,s;return t=Mt,33===e.charCodeAt(Mt)?(r=le,Mt++):(r=P,0===Vt&&i(pe)),r!==P?(n=a(),n!==P?(s=f(),s!==P?(Nt=t,r=fe(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P),t===P&&(t=h()),t}function h(){var t,r,n,s,u,c;return t=Mt,40===e.charCodeAt(Mt)?(r=he,Mt++):(r=P,0===Vt&&i(de)),r!==P?(n=a(),n!==P?(s=l(),s!==P?(u=a(),u!==P?(41===e.charCodeAt(Mt)?(c=ve,Mt++):(c=P,0===Vt&&i(ye)),c!==P?(Nt=t,r=ge(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P)):(Mt=t,t=P),t===P&&(t=d()),t}function d(){var e;return e=v(),e===P&&(e=g()),e}function v(){var t,r;return t=y(),t===P&&(t=Mt,e.substr(Mt,2)===Ae?(r=Ae,Mt+=2):(r=P,0===Vt&&i(xe)),r!==P&&(Nt=t,r=Re()),t=r,t===P&&(t=Mt,e.substr(Mt,2)===me?(r=me,Mt+=2):(r=P,0===Vt&&i(qe)),r!==P&&(Nt=t,r=Ce()),t=r,t===P&&(t=Mt,e.substr(Mt,2)===we?(r=we,Mt+=2):(r=P,0===Vt&&i(Ee)),r!==P&&(Nt=t,r=be()),t=r,t===P&&(t=Mt,e.substr(Mt,2)===Fe?(r=Fe,Mt+=2):(r=P,0===Vt&&i(Ue)),r!==P&&(Nt=t,r=je()),t=r)))),t}function y(){var t,r;return t=Mt,e.substr(Mt,4)===Te?(r=Te,Mt+=4):(r=P,0===Vt&&i(_e)),r!==P&&(Nt=t,r=Se()),t=r,t===P&&(t=Mt,e.substr(Mt,5)===ke?(r=ke,Mt+=5):(r=P,0===Vt&&i(Be)),r!==P&&(Nt=t,r=Ie()),t=r),t}function g(){var t,r,n,s;if(t=Mt,e.substr(Mt,2)===ze?(r=ze,Mt+=2):(r=P,0===Vt&&i(De)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=A(),s!==P?(Nt=t,r=Ge(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,2)===He?(r=He,Mt+=2):(r=P,0===Vt&&i(Je)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=Ke(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,2)===Le?(r=Le,Mt+=2):(r=P,0===Vt&&i(Me)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=Ne(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,3)===Oe?(r=Oe,Mt+=3):(r=P,0===Vt&&i(Pe)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=Qe(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,3)===Ve?(r=Ve,Mt+=3):(r=P,0===Vt&&i(We)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=Xe(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,2)===Ye?(r=Ye,Mt+=2):(r=P,0===Vt&&i(Ze)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=$e(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,2)===et?(r=et,Mt+=2):(r=P,0===Vt&&i(tt)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=rt(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,3)===nt?(r=nt,Mt+=3):(r=P,0===Vt&&i(it)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=st(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,3)===ut?(r=ut,Mt+=3):(r=P,0===Vt&&i(ct)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=ot(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,e.substr(Mt,2)===at?(r=at,Mt+=2):(r=P,0===Vt&&i(lt)),r!==P){if(n=[],s=c(),s!==P)for(;s!==P;)n.push(s),s=c();else n=P;n!==P?(s=x(),s!==P?(Nt=t,r=pt(s),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;t===P&&(t=Mt,r=x(),r!==P&&(Nt=t,r=pt(r)),t=r)}}}}}}}}}return t}function A(){var t,r,n,s;if(Vt++,t=Mt,ht.test(e.charAt(Mt))?(r=e.charAt(Mt),Mt++):(r=P,0===Vt&&i(dt)),r===P&&(r=null),r!==P){if(n=[],vt.test(e.charAt(Mt))?(s=e.charAt(Mt),Mt++):(s=P,0===Vt&&i(yt)),s!==P)for(;s!==P;)n.push(s),vt.test(e.charAt(Mt))?(s=e.charAt(Mt),Mt++):(s=P,0===Vt&&i(yt));else n=P;n!==P?(ht.test(e.charAt(Mt))?(s=e.charAt(Mt),Mt++):(s=P,0===Vt&&i(dt)),s===P&&(s=null),s!==P?(Nt=t,r=gt(n),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;return Vt--,t===P&&(r=P,0===Vt&&i(ft)),t}function x(){var t,r,n,s;if(Vt++,t=Mt,34===e.charCodeAt(Mt)?(r=xt,Mt++):(r=P,0===Vt&&i(Rt)),r!==P){for(n=[],s=R();s!==P;)n.push(s),s=R();n!==P?(34===e.charCodeAt(Mt)?(s=xt,Mt++):(s=P,0===Vt&&i(Rt)),s!==P?(Nt=t,r=mt(n),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P){if(t=Mt,39===e.charCodeAt(Mt)?(r=qt,Mt++):(r=P,0===Vt&&i(Ct)),r!==P){for(n=[],s=m();s!==P;)n.push(s),s=m();n!==P?(39===e.charCodeAt(Mt)?(s=qt,Mt++):(s=P,0===Vt&&i(Ct)),s!==P?(Nt=t,r=mt(n),t=r):(Mt=t,t=P)):(Mt=t,t=P)}else Mt=t,t=P;if(t===P)if(t=Mt,r=Mt,Vt++,n=o(),Vt--,n===P?r=void 0:(Mt=r,r=P),r!==P){if(n=[],s=q(),s!==P)for(;s!==P;)n.push(s),s=q();else n=P;n!==P?(Nt=t,r=mt(n),t=r):(Mt=t,t=P)}else Mt=t,t=P}return Vt--,t===P&&(r=P,0===Vt&&i(At)),t}function R(){var t,r,n;return t=Mt,r=Mt,Vt++,wt.test(e.charAt(Mt))?(n=e.charAt(Mt),Mt++):(n=P,0===Vt&&i(Et)),Vt--,n===P?r=void 0:(Mt=r,r=P),r!==P?(e.length>Mt?(n=e.charAt(Mt),Mt++):(n=P,0===Vt&&i(bt)),n!==P?(Nt=t,r=Ft(n),t=r):(Mt=t,t=P)):(Mt=t,t=P),t===P&&(t=Mt,92===e.charCodeAt(Mt)?(r=Ut,Mt++):(r=P,0===Vt&&i(jt)),r!==P?(n=C(),n!==P?(Nt=t,r=Ft(n),t=r):(Mt=t,t=P)):(Mt=t,t=P)),t}function m(){var t,r,n;return t=Mt,r=Mt,Vt++,Tt.test(e.charAt(Mt))?(n=e.charAt(Mt),Mt++):(n=P,0===Vt&&i(_t)),Vt--,n===P?r=void 0:(Mt=r,r=P),r!==P?(e.length>Mt?(n=e.charAt(Mt),Mt++):(n=P,0===Vt&&i(bt)),n!==P?(Nt=t,r=Ft(n),t=r):(Mt=t,t=P)):(Mt=t,t=P),t===P&&(t=Mt,92===e.charCodeAt(Mt)?(r=Ut,Mt++):(r=P,0===Vt&&i(jt)),r!==P?(n=C(),n!==P?(Nt=t,r=Ft(n),t=r):(Mt=t,t=P)):(Mt=t,t=P)),t}function q(){var t,r,n;return t=Mt,r=Mt,Vt++,n=c(),Vt--,n===P?r=void 0:(Mt=r,r=P),r!==P?(e.length>Mt?(n=e.charAt(Mt),Mt++):(n=P,0===Vt&&i(bt)),n!==P?(Nt=t,r=Ft(n),t=r):(Mt=t,t=P)):(Mt=t,t=P),t}function C(){var t,r;return St.test(e.charAt(Mt))?(t=e.charAt(Mt),Mt++):(t=P,0===Vt&&i(kt)),t===P&&(t=Mt,110===e.charCodeAt(Mt)?(r=Bt,Mt++):(r=P,0===Vt&&i(It)),r!==P&&(Nt=t,r=zt()),t=r,t===P&&(t=Mt,114===e.charCodeAt(Mt)?(r=Dt,Mt++):(r=P,0===Vt&&i(Gt)),r!==P&&(Nt=t,r=Ht()),t=r,t===P&&(t=Mt,116===e.charCodeAt(Mt)?(r=Jt,Mt++):(r=P,0===Vt&&i(Kt)),r!==P&&(Nt=t,r=Lt()),t=r))),t}function w(e,t){function r(){return e.apply(this,arguments)||t.apply(this,arguments)}return r.desc=e.desc+" or "+t.desc,r}function E(e,t){function r(){return e.apply(this,arguments)&&t.apply(this,arguments)}return r.desc=e.desc+" and "+t.desc,r}function b(e){function t(){return!e.apply(this,arguments)}return t.desc="not "+e.desc,t}function F(e){function t(){return e.apply(this,arguments)}return t.desc="("+e.desc+")",t}function U(e){return!0}function j(e){return!1}function T(e){if(e.response)for(var t=Wt.ResponseUtils.getContentType(e.response),r=Xt.length;r--;)if(Xt[r].test(t))return!0;return!1}function _(e){function t(t){return t.response&&t.response.status_code===e}return t.desc="resp. code is "+e,t}function S(e){function t(t){return t.request&&e.test(t.request.host)}return e=new RegExp(e,"i"),t.desc="domain matches "+e,t}function k(e){return!!e.error}function B(e){function t(t){return t.request&&Wt.RequestUtils.match_header(t.request,e)||t.response&&Wt.ResponseUtils.match_header(t.response,e)}return e=new RegExp(e,"i"),t.desc="header matches "+e,t}function I(e){function t(t){return t.request&&Wt.RequestUtils.match_header(t.request,e)}return e=new RegExp(e,"i"),t.desc="req. header matches "+e,t}function z(e){function t(t){return t.response&&Wt.ResponseUtils.match_header(t.response,e)}return e=new RegExp(e,"i"),t.desc="resp. header matches "+e,t}function D(e){function t(t){return t.request&&e.test(t.request.method)}return e=new RegExp(e,"i"),t.desc="method matches "+e,t}function G(e){return e.request&&!e.response}function H(e){return!!e.response}function J(e){function t(t){return t.request&&e.test(Wt.RequestUtils.getContentType(t.request))||t.response&&e.test(Wt.ResponseUtils.getContentType(t.response))}return e=new RegExp(e,"i"),t.desc="content type matches "+e,t}function K(e){function t(t){return t.request&&e.test(Wt.RequestUtils.getContentType(t.request))}return e=new RegExp(e,"i"),t.desc="req. content type matches "+e,t}function L(e){function t(t){return t.response&&e.test(Wt.ResponseUtils.getContentType(t.response))}return e=new RegExp(e,"i"),t.desc="resp. content type matches "+e,t}function M(e){function t(t){return t.request&&e.test(Wt.RequestUtils.pretty_url(t.request))}return e=new RegExp(e,"i"),t.desc="url matches "+e,t}var N,O=arguments.length>1?arguments[1]:{},P={},Q={start:u},V=u,W={type:"other",description:"filter expression"},X=function(e){return e},Y={type:"other",description:"whitespace"},Z=/^[ \t\n\r]/,$={type:"class",value:"[ \\t\\n\\r]",description:"[ \\t\\n\\r]"},ee={type:"other",description:"control character"},te=/^[|&!()~"]/,re={type:"class",value:'[|&!()~"]',description:'[|&!()~"]'},ne={type:"other",description:"optional whitespace"},ie="|",se={type:"literal",value:"|",description:'"|"'},ue=function(e,t){return w(e,t)},ce="&",oe={type:"literal",value:"&",description:'"&"'},ae=function(e,t){return E(e,t)},le="!",pe={type:"literal",value:"!",description:'"!"'},fe=function(e){return b(e)},he="(",de={type:"literal",value:"(",description:'"("'},ve=")",ye={type:"literal",value:")",description:'")"'},ge=function(e){return F(e)},Ae="~a",xe={type:"literal",value:"~a",description:'"~a"'},Re=function(){return T},me="~e",qe={type:"literal",value:"~e",description:'"~e"'},Ce=function(){return k},we="~q",Ee={type:"literal",value:"~q",description:'"~q"'},be=function(){return G},Fe="~s",Ue={type:"literal",value:"~s",description:'"~s"'},je=function(){return H},Te="true",_e={type:"literal",value:"true",description:'"true"'},Se=function(){return U},ke="false",Be={type:"literal",value:"false",description:'"false"'},Ie=function(){return j},ze="~c",De={type:"literal",value:"~c",description:'"~c"'},Ge=function(e){return _(e)},He="~d",Je={type:"literal",value:"~d",description:'"~d"'},Ke=function(e){return S(e)},Le="~h",Me={type:"literal",value:"~h",description:'"~h"'},Ne=function(e){return B(e)},Oe="~hq",Pe={type:"literal",value:"~hq",description:'"~hq"'},Qe=function(e){return I(e)},Ve="~hs",We={type:"literal",value:"~hs",description:'"~hs"'},Xe=function(e){return z(e)},Ye="~m",Ze={type:"literal",value:"~m",description:'"~m"'},$e=function(e){return D(e)},et="~t",tt={type:"literal",value:"~t",description:'"~t"'},rt=function(e){return J(e)},nt="~tq",it={type:"literal",value:"~tq",description:'"~tq"'},st=function(e){return K(e)},ut="~ts",ct={type:"literal",value:"~ts",description:'"~ts"'},ot=function(e){return L(e)},at="~u",lt={type:"literal",value:"~u",description:'"~u"'},pt=function(e){return M(e)},ft={type:"other",description:"integer"},ht=/^['"]/,dt={type:"class",value:"['\"]",description:"['\"]"},vt=/^[0-9]/,yt={type:"class",value:"[0-9]",description:"[0-9]"},gt=function(e){return parseInt(e.join(""),10)},At={type:"other",description:"string"},xt='"',Rt={type:"literal",value:'"',description:'"\\""'},mt=function(e){return e.join("")},qt="'",Ct={type:"literal",value:"'",description:'"\'"'},wt=/^["\\]/,Et={type:"class",value:'["\\\\]',description:'["\\\\]'},bt={type:"any",description:"any character"},Ft=function(e){return e},Ut="\\",jt={type:"literal",value:"\\",description:'"\\\\"'},Tt=/^['\\]/,_t={type:"class",value:"['\\\\]",description:"['\\\\]"},St=/^['"\\]/,kt={type:"class",value:"['\"\\\\]",description:"['\"\\\\]"},Bt="n",It={type:"literal",value:"n",description:'"n"'},zt=function(){return"\n"},Dt="r",Gt={type:"literal",value:"r",description:'"r"'},Ht=function(){return"\r"},Jt="t",Kt={type:"literal",value:"t",description:'"t"'},Lt=function(){return" "},Mt=0,Nt=0,Ot=[{line:1,column:1,seenCR:!1}],Pt=0,Qt=[],Vt=0;if("startRule"in O){if(!(O.startRule in Q))throw new Error("Can't start parsing from rule \""+O.startRule+'".');V=Q[O.startRule]}var Wt=require("../flow/utils.js");U.desc="true",j.desc="false";var Xt=[new RegExp("text/javascript"),new RegExp("application/x-javascript"),new RegExp("application/javascript"),new RegExp("text/css"),new RegExp("image/.*"),new RegExp("application/x-shockwave-flash")];if(T.desc="is asset",k.desc="has error",G.desc="has no response",H.desc="has response",N=V(),N!==P&&Mt===e.length)return N;throw N!==P&&Mt<e.length&&i({type:"end",description:"end of input"}),s(null,Qt,Pt<e.length?e.charAt(Pt):null,Pt<e.length?n(Pt,Pt+1):n(Pt,Pt))}return e(t,Error),{SyntaxError:t,parse:r}}(); -},{"../flow/utils.js":40}],40:[function(require,module,exports){ +},{"../flow/utils.js":50}],50:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseHttpVersion=exports.isValidHttpVersion=exports.parseUrl=exports.ResponseUtils=exports.RequestUtils=exports.MessageUtils=void 0;var _lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_jquery=require("jquery"),_jquery2=_interopRequireDefault(_jquery),defaultPorts={http:80,https:443},MessageUtils=exports.MessageUtils={getContentType:function(e){var t=this.get_first_header(e,/^Content-Type$/i);return t?t.split(";")[0].trim():void 0},get_first_header:function(e,t){if(e._headerLookups||Object.defineProperty(e,"_headerLookups",{value:{},configurable:!1,enumerable:!1,writable:!1}),!(t in e._headerLookups)){for(var r,s=0;s<e.headers.length;s++)if(e.headers[s][0].match(t)){r=e.headers[s];break}e._headerLookups[t]=r?r[1]:void 0}return e._headerLookups[t]},match_header:function(e,t){for(var r=e.headers,s=r.length;s--;)if(t.test(r[s].join(" ")))return r[s];return!1},getContentURL:function(e,t){return t===e.request?t="request":t===e.response&&(t="response"),"/flows/"+e.id+"/"+t+"/content"},getContent:function(e,t){var r=MessageUtils.getContentURL(e,t);return _jquery2["default"].get(r)}},RequestUtils=exports.RequestUtils=_lodash2["default"].extend(MessageUtils,{pretty_host:function(e){return e.host},pretty_url:function(e){var t="";return defaultPorts[e.scheme]!==e.port&&(t=":"+e.port),e.scheme+"://"+this.pretty_host(e)+t+e.path}}),ResponseUtils=exports.ResponseUtils=_lodash2["default"].extend(MessageUtils,{}),parseUrl_regex=/^(?:(https?):\/\/)?([^\/:]+)?(?::(\d+))?(\/.*)?$/i,parseUrl=exports.parseUrl=function(e){var t=parseUrl_regex.exec(e);if(!t)return!1;var r=t[1],s=t[2],o=parseInt(t[3]),n=t[4];r&&(o=o||defaultPorts[r]);var i={};return r&&(i.scheme=r),s&&(i.host=s),o&&(i.port=o),n&&(i.path=n),i},isValidHttpVersion_regex=/^HTTP\/\d+(\.\d+)*$/i,isValidHttpVersion=exports.isValidHttpVersion=function(e){return isValidHttpVersion_regex.test(e)},parseHttpVersion=exports.parseHttpVersion=function(e){return e=e.replace("HTTP/","").split("."),_lodash2["default"].map(e,function(e){return parseInt(e)})}; -},{"jquery":"jquery","lodash":"lodash"}],41:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],51:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function DictStore(){_events.EventEmitter.call(this),this.reset()}function LiveStoreMixin(e){this.type=e,this._updates_before_fetch=void 0,this._fetchxhr=!1,this.handle=this.handle.bind(this),_dispatcher.AppDispatcher.register(this.handle),window.ws&&window.ws.readyState===WebSocket.CONNECTING||this.fetch()}function LiveDictStore(e){DictStore.call(this),LiveStoreMixin.call(this,e)}function SettingsStore(){return new LiveDictStore(_actions.ActionTypes.SETTINGS_STORE)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.SettingsStore=SettingsStore;var _lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_jquery=require("jquery"),_jquery2=_interopRequireDefault(_jquery),_events=require("events"),_actions=require("../actions.js"),_dispatcher=require("../dispatcher.js");_lodash2["default"].extend(DictStore.prototype,_events.EventEmitter.prototype,{update:function(e){_lodash2["default"].merge(this.dict,e),this.emit("recalculate")},reset:function(e){this.dict=e||{},this.emit("recalculate")}}),_lodash2["default"].extend(LiveStoreMixin.prototype,{handle:function(e){return e.type===_actions.ActionTypes.CONNECTION_OPEN?this.fetch():void(e.type===this.type&&(e.cmd===_actions.StoreCmds.RESET?this.fetch(e.data):this._updates_before_fetch?(console.log("defer update",e),this._updates_before_fetch.push(e)):this[e.cmd](e.data)))},close:function(){_dispatcher.AppDispatcher.unregister(this.handle)},fetch:function(e){console.log("fetch "+this.type),this._fetchxhr&&this._fetchxhr.abort(),this._updates_before_fetch=[],e?this.handle_fetch(e):this._fetchxhr=_jquery2["default"].getJSON("/"+this.type).done(function(e){this.handle_fetch(e.data)}.bind(this)).fail(function(){console.error("Could not fetch "+this.type)}.bind(this))},handle_fetch:function(e){this._fetchxhr=!1,console.log(this.type+" fetched.",this._updates_before_fetch),this.reset(e);var t=this._updates_before_fetch;this._updates_before_fetch=!1;for(var i=0;i<t.length;i++)this.handle(t[i])}}),_lodash2["default"].extend(LiveDictStore.prototype,DictStore.prototype,LiveStoreMixin.prototype); -},{"../actions.js":2,"../dispatcher.js":31,"events":1,"jquery":"jquery","lodash":"lodash"}],42:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":41,"events":1,"jquery":"jquery","lodash":"lodash"}],52:[function(require,module,exports){ "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}function reverseString(e){return String.fromCharCode.apply(String,_lodash2["default"].map(e.split(""),function(e){return 65535-e.charCodeAt(0)}))+end}function getCookie(e){var r=document.cookie.match(new RegExp("\\b"+e+"=([^;]*)\\b"));return r?r[1]:void 0}function fetchApi(e,r){return e+=-1===e.indexOf("?")?"?"+xsrf:"&"+xsrf,fetch(e,_extends({},r,{credentials:"same-origin"}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatTimeStamp=exports.formatTimeDelta=exports.formatSize=exports.Key=void 0;var _extends=Object.assign||function(e){for(var r=1;r<arguments.length;r++){var t=arguments[r];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o])}return e};exports.reverseString=reverseString,exports.fetchApi=fetchApi;var _jquery=require("jquery"),_jquery2=_interopRequireDefault(_jquery),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_actions=require("./actions.js"),_actions2=_interopRequireDefault(_actions);window.$=_jquery2["default"],window._=_lodash2["default"],window.React=require("react");for(var Key=exports.Key={UP:38,DOWN:40,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,LEFT:37,RIGHT:39,ENTER:13,ESC:27,TAB:9,SPACE:32,BACKSPACE:8,SHIFT:16},i=65;90>=i;i++)Key[String.fromCharCode(i)]=i;var formatSize=exports.formatSize=function(e){if(0===e)return"0";for(var r=["b","kb","mb","gb","tb"],t=0;t<r.length&&!(Math.pow(1024,t+1)>e);t++);var o;return o=e%Math.pow(1024,t)===0?0:1,(e/Math.pow(1024,t)).toFixed(o)+r[t]},formatTimeDelta=exports.formatTimeDelta=function(e){for(var r=e,t=["ms","s","min","h"],o=[1e3,60,60],a=0;Math.abs(r)>=o[a]&&a<o.length;)r/=o[a],a++;return Math.round(r)+t[a]},formatTimeStamp=exports.formatTimeStamp=function(e){var r=new Date(1e3*e).toISOString();return r.replace("T"," ").replace("Z","")},end=String.fromCharCode(65535),xsrf="_xsrf="+getCookie("_xsrf");_jquery2["default"].ajaxPrefilter(function(e){["post","put","delete"].indexOf(e.type.toLowerCase())>=0&&"/"===e.url[0]&&(-1===e.url.indexOf("?")?e.url+="?"+xsrf:e.url+="&"+xsrf)}),(0,_jquery2["default"])(document).ajaxError(function(e,r,t,o){if("abort"!==o){var a=r.responseText;console.error(o,a,arguments),alert(a)}}); },{"./actions.js":2,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[3]) diff --git a/netlib/http/http2/__init__.py b/netlib/http/http2/__init__.py index 633e6a20..6a979a0d 100644 --- a/netlib/http/http2/__init__.py +++ b/netlib/http/http2/__init__.py @@ -1,8 +1,6 @@ from __future__ import absolute_import, print_function, division -from .connections import HTTP2Protocol from netlib.http.http2 import framereader __all__ = [ - "HTTP2Protocol", "framereader", ] diff --git a/netlib/http/http2/connections.py b/netlib/http/http2/connections.py deleted file mode 100644 index 8f246feb..00000000 --- a/netlib/http/http2/connections.py +++ /dev/null @@ -1,432 +0,0 @@ -from __future__ import (absolute_import, print_function, division) -import itertools -import time - -import hyperframe.frame - -from hpack.hpack import Encoder, Decoder -from netlib import utils, strutils -from netlib.http import url -import netlib.http.headers -import netlib.http.response -import netlib.http.request -from netlib.http.http2 import framereader - - -class TCPHandler(object): - - def __init__(self, rfile, wfile=None): - self.rfile = rfile - self.wfile = wfile - - -class HTTP2Protocol(object): - - ERROR_CODES = utils.BiDi( - NO_ERROR=0x0, - PROTOCOL_ERROR=0x1, - INTERNAL_ERROR=0x2, - FLOW_CONTROL_ERROR=0x3, - SETTINGS_TIMEOUT=0x4, - STREAM_CLOSED=0x5, - FRAME_SIZE_ERROR=0x6, - REFUSED_STREAM=0x7, - CANCEL=0x8, - COMPRESSION_ERROR=0x9, - CONNECT_ERROR=0xa, - ENHANCE_YOUR_CALM=0xb, - INADEQUATE_SECURITY=0xc, - HTTP_1_1_REQUIRED=0xd - ) - - CLIENT_CONNECTION_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' - - HTTP2_DEFAULT_SETTINGS = { - hyperframe.frame.SettingsFrame.HEADER_TABLE_SIZE: 4096, - hyperframe.frame.SettingsFrame.ENABLE_PUSH: 1, - hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: None, - hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: 2 ** 16 - 1, - hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE: 2 ** 14, - hyperframe.frame.SettingsFrame.MAX_HEADER_LIST_SIZE: None, - } - - def __init__( - self, - tcp_handler=None, - rfile=None, - wfile=None, - is_server=False, - dump_frames=False, - encoder=None, - decoder=None, - unhandled_frame_cb=None, - ): - self.tcp_handler = tcp_handler or TCPHandler(rfile, wfile) - self.is_server = is_server - self.dump_frames = dump_frames - self.encoder = encoder or Encoder() - self.decoder = decoder or Decoder() - self.unhandled_frame_cb = unhandled_frame_cb - - self.http2_settings = self.HTTP2_DEFAULT_SETTINGS.copy() - self.current_stream_id = None - self.connection_preface_performed = False - - def read_request( - self, - __rfile, - include_body=True, - body_size_limit=None, - allow_empty=False, - ): - if body_size_limit is not None: - raise NotImplementedError() - - self.perform_connection_preface() - - timestamp_start = time.time() - if hasattr(self.tcp_handler.rfile, "reset_timestamps"): - self.tcp_handler.rfile.reset_timestamps() - - stream_id, headers, body = self._receive_transmission( - include_body=include_body, - ) - - if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"): - # more accurate timestamp_start - timestamp_start = self.tcp_handler.rfile.first_byte_timestamp - - timestamp_end = time.time() - - authority = headers.get(':authority', b'') - method = headers.get(':method', 'GET') - scheme = headers.get(':scheme', 'https') - path = headers.get(':path', '/') - - headers.clear(":method") - headers.clear(":scheme") - headers.clear(":path") - - host = None - port = None - - if path == '*' or path.startswith("/"): - first_line_format = "relative" - elif method == 'CONNECT': - first_line_format = "authority" - if ":" in authority: - host, port = authority.split(":", 1) - else: - host = authority - else: - first_line_format = "absolute" - # FIXME: verify if path or :host contains what we need - scheme, host, port, _ = url.parse(path) - scheme = scheme.decode('ascii') - host = host.decode('ascii') - - if host is None: - host = 'localhost' - if port is None: - port = 80 if scheme == 'http' else 443 - port = int(port) - - request = netlib.http.request.Request( - first_line_format, - method.encode('ascii'), - scheme.encode('ascii'), - host.encode('ascii'), - port, - path.encode('ascii'), - b"HTTP/2.0", - headers, - body, - timestamp_start, - timestamp_end, - ) - request.stream_id = stream_id - - return request - - def read_response( - self, - __rfile, - request_method=b'', - body_size_limit=None, - include_body=True, - stream_id=None, - ): - if body_size_limit is not None: - raise NotImplementedError() - - self.perform_connection_preface() - - timestamp_start = time.time() - if hasattr(self.tcp_handler.rfile, "reset_timestamps"): - self.tcp_handler.rfile.reset_timestamps() - - stream_id, headers, body = self._receive_transmission( - stream_id=stream_id, - include_body=include_body, - ) - - if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"): - # more accurate timestamp_start - timestamp_start = self.tcp_handler.rfile.first_byte_timestamp - - if include_body: - timestamp_end = time.time() - else: - timestamp_end = None - - response = netlib.http.response.Response( - b"HTTP/2.0", - int(headers.get(':status', 502)), - b'', - headers, - body, - timestamp_start=timestamp_start, - timestamp_end=timestamp_end, - ) - response.stream_id = stream_id - - return response - - def assemble(self, message): - if isinstance(message, netlib.http.request.Request): - return self.assemble_request(message) - elif isinstance(message, netlib.http.response.Response): - return self.assemble_response(message) - else: - raise ValueError("HTTP message not supported.") - - def assemble_request(self, request): - assert isinstance(request, netlib.http.request.Request) - - authority = self.tcp_handler.sni if self.tcp_handler.sni else self.tcp_handler.address.host - if self.tcp_handler.address.port != 443: - authority += ":%d" % self.tcp_handler.address.port - - headers = request.headers.copy() - - if ':authority' not in headers: - headers.insert(0, b':authority', authority.encode('ascii')) - headers.insert(0, b':scheme', request.scheme.encode('ascii')) - headers.insert(0, b':path', request.path.encode('ascii')) - headers.insert(0, b':method', request.method.encode('ascii')) - - if hasattr(request, 'stream_id'): - stream_id = request.stream_id - else: - stream_id = self._next_stream_id() - - return list(itertools.chain( - self._create_headers(headers, stream_id, end_stream=(request.body is None or len(request.body) == 0)), - self._create_body(request.body, stream_id))) - - def assemble_response(self, response): - assert isinstance(response, netlib.http.response.Response) - - headers = response.headers.copy() - - if ':status' not in headers: - headers.insert(0, b':status', strutils.always_bytes(response.status_code)) - - if hasattr(response, 'stream_id'): - stream_id = response.stream_id - else: - stream_id = self._next_stream_id() - - return list(itertools.chain( - self._create_headers(headers, stream_id, end_stream=(response.body is None or len(response.body) == 0)), - self._create_body(response.body, stream_id), - )) - - def perform_connection_preface(self, force=False): - if force or not self.connection_preface_performed: - if self.is_server: - self.perform_server_connection_preface(force) - else: - self.perform_client_connection_preface(force) - - def perform_server_connection_preface(self, force=False): - if force or not self.connection_preface_performed: - self.connection_preface_performed = True - - magic_length = len(self.CLIENT_CONNECTION_PREFACE) - magic = self.tcp_handler.rfile.safe_read(magic_length) - assert magic == self.CLIENT_CONNECTION_PREFACE - - frm = hyperframe.frame.SettingsFrame(settings={ - hyperframe.frame.SettingsFrame.ENABLE_PUSH: 0, - hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1, - }) - self.send_frame(frm, hide=True) - self._receive_settings(hide=True) - - def perform_client_connection_preface(self, force=False): - if force or not self.connection_preface_performed: - self.connection_preface_performed = True - - self.tcp_handler.wfile.write(self.CLIENT_CONNECTION_PREFACE) - - self.send_frame(hyperframe.frame.SettingsFrame(), hide=True) - self._receive_settings(hide=True) # server announces own settings - self._receive_settings(hide=True) # server acks my settings - - def send_frame(self, frm, hide=False): - raw_bytes = frm.serialize() - self.tcp_handler.wfile.write(raw_bytes) - self.tcp_handler.wfile.flush() - if not hide and self.dump_frames: # pragma no cover - print(frm.human_readable(">>")) - - def read_frame(self, hide=False): - while True: - frm = framereader.http2_read_frame(self.tcp_handler.rfile) - if not hide and self.dump_frames: # pragma no cover - print(frm.human_readable("<<")) - - if isinstance(frm, hyperframe.frame.PingFrame): - raw_bytes = hyperframe.frame.PingFrame(flags=['ACK'], payload=frm.payload).serialize() - self.tcp_handler.wfile.write(raw_bytes) - self.tcp_handler.wfile.flush() - continue - if isinstance(frm, hyperframe.frame.SettingsFrame) and 'ACK' not in frm.flags: - self._apply_settings(frm.settings, hide) - if isinstance(frm, hyperframe.frame.DataFrame) and frm.flow_controlled_length > 0: - self._update_flow_control_window(frm.stream_id, frm.flow_controlled_length) - return frm - - def check_alpn(self): - alp = self.tcp_handler.get_alpn_proto_negotiated() - if alp != b'h2': - raise NotImplementedError( - "HTTP2Protocol can not handle unknown ALP: %s" % alp) - return True - - def _handle_unexpected_frame(self, frm): - if isinstance(frm, hyperframe.frame.SettingsFrame): - return - if self.unhandled_frame_cb: - self.unhandled_frame_cb(frm) - - def _receive_settings(self, hide=False): - while True: - frm = self.read_frame(hide) - if isinstance(frm, hyperframe.frame.SettingsFrame): - break - else: - self._handle_unexpected_frame(frm) - - def _next_stream_id(self): - if self.current_stream_id is None: - if self.is_server: - # servers must use even stream ids - self.current_stream_id = 2 - else: - # clients must use odd stream ids - self.current_stream_id = 1 - else: - self.current_stream_id += 2 - return self.current_stream_id - - def _apply_settings(self, settings, hide=False): - for setting, value in settings.items(): - old_value = self.http2_settings[setting] - if not old_value: - old_value = '-' - self.http2_settings[setting] = value - - frm = hyperframe.frame.SettingsFrame(flags=['ACK']) - self.send_frame(frm, hide) - - def _update_flow_control_window(self, stream_id, increment): - frm = hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=increment) - self.send_frame(frm) - frm = hyperframe.frame.WindowUpdateFrame(stream_id=stream_id, window_increment=increment) - self.send_frame(frm) - - def _create_headers(self, headers, stream_id, end_stream=True): - def frame_cls(chunks): - for i in chunks: - if i == 0: - yield hyperframe.frame.HeadersFrame, i - else: - yield hyperframe.frame.ContinuationFrame, i - - header_block_fragment = self.encoder.encode(headers.fields) - - chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] - chunks = range(0, len(header_block_fragment), chunk_size) - frms = [frm_cls( - flags=[], - stream_id=stream_id, - data=header_block_fragment[i:i + chunk_size]) for frm_cls, i in frame_cls(chunks)] - - frms[-1].flags.add('END_HEADERS') - if end_stream: - frms[0].flags.add('END_STREAM') - - if self.dump_frames: # pragma no cover - for frm in frms: - print(frm.human_readable(">>")) - - return [frm.serialize() for frm in frms] - - def _create_body(self, body, stream_id): - if body is None or len(body) == 0: - return b'' - - chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] - chunks = range(0, len(body), chunk_size) - frms = [hyperframe.frame.DataFrame( - flags=[], - stream_id=stream_id, - data=body[i:i + chunk_size]) for i in chunks] - frms[-1].flags.add('END_STREAM') - - if self.dump_frames: # pragma no cover - for frm in frms: - print(frm.human_readable(">>")) - - return [frm.serialize() for frm in frms] - - def _receive_transmission(self, stream_id=None, include_body=True): - if not include_body: - raise NotImplementedError() - - body_expected = True - - header_blocks = b'' - body = b'' - - while True: - frm = self.read_frame() - if ( - (isinstance(frm, hyperframe.frame.HeadersFrame) or isinstance(frm, hyperframe.frame.ContinuationFrame)) and - (stream_id is None or frm.stream_id == stream_id) - ): - stream_id = frm.stream_id - header_blocks += frm.data - if 'END_STREAM' in frm.flags: - body_expected = False - if 'END_HEADERS' in frm.flags: - break - else: - self._handle_unexpected_frame(frm) - - while body_expected: - frm = self.read_frame() - if isinstance(frm, hyperframe.frame.DataFrame) and frm.stream_id == stream_id: - body += frm.data - if 'END_STREAM' in frm.flags: - break - else: - self._handle_unexpected_frame(frm) - - headers = netlib.http.headers.Headers( - (k.encode('ascii'), v.encode('ascii')) for k, v in self.decoder.decode(header_blocks) - ) - - return stream_id, headers, body diff --git a/pathod/pathoc.py b/pathod/pathoc.py index 478ce2a2..c6783878 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -11,18 +11,18 @@ import time import OpenSSL.crypto import six +import logging +from netlib.tutils import treq +from netlib import strutils from netlib import tcp, certutils, websockets, socks from netlib import exceptions from netlib.http import http1 -from netlib.http import http2 from netlib import basethread -from pathod import log, language +from . import log, language +from .protocols import http2 -import logging -from netlib.tutils import treq -from netlib import strutils logging.getLogger("hpack").setLevel(logging.WARNING) @@ -227,7 +227,7 @@ class Pathoc(tcp.TCPClient): "Pathoc might not be working as expected without ALPN.", timestamp=False ) - self.protocol = http2.HTTP2Protocol(self, dump_frames=self.http2_framedump) + self.protocol = http2.HTTP2StateProtocol(self, dump_frames=self.http2_framedump) else: self.protocol = http1 @@ -241,8 +241,8 @@ class Pathoc(tcp.TCPClient): def http_connect(self, connect_to): self.wfile.write( - 'CONNECT %s:%s HTTP/1.1\r\n' % tuple(connect_to) + - '\r\n' + b'CONNECT %s:%d HTTP/1.1\r\n' % (connect_to[0].encode("idna"), connect_to[1]) + + b'\r\n' ) self.wfile.flush() try: diff --git a/pathod/protocols/http.py b/pathod/protocols/http.py index 7736df4b..2ede2591 100644 --- a/pathod/protocols/http.py +++ b/pathod/protocols/http.py @@ -17,15 +17,15 @@ class HTTPProtocol(object): """ self.pathod_handler.wfile.write( - 'HTTP/1.1 200 Connection established\r\n' + - ('Proxy-agent: %s\r\n' % version.PATHOD) + - '\r\n' + b'HTTP/1.1 200 Connection established\r\n' + + (b'Proxy-agent: %s\r\n' % version.PATHOD.encode()) + + b'\r\n' ) self.pathod_handler.wfile.flush() if not self.pathod_handler.server.ssloptions.not_after_connect: try: cert, key, chain_file_ = self.pathod_handler.server.ssloptions.get_cert( - connect[0] + connect[0].encode() ) self.pathod_handler.convert_to_ssl( cert, diff --git a/pathod/protocols/http2.py b/pathod/protocols/http2.py index 3f45ec80..c8728940 100644 --- a/pathod/protocols/http2.py +++ b/pathod/protocols/http2.py @@ -1,12 +1,445 @@ -from netlib.http import http2 +from __future__ import (absolute_import, print_function, division) + +import itertools +import time + +import hyperframe.frame +from hpack.hpack import Encoder, Decoder + +from netlib import utils, strutils +from netlib.http import url +from netlib.http.http2 import framereader +import netlib.http.headers +import netlib.http.response +import netlib.http.request + from .. import language -class HTTP2Protocol: +class TCPHandler(object): + + def __init__(self, rfile, wfile=None): + self.rfile = rfile + self.wfile = wfile + + +class HTTP2StateProtocol(object): + + ERROR_CODES = utils.BiDi( + NO_ERROR=0x0, + PROTOCOL_ERROR=0x1, + INTERNAL_ERROR=0x2, + FLOW_CONTROL_ERROR=0x3, + SETTINGS_TIMEOUT=0x4, + STREAM_CLOSED=0x5, + FRAME_SIZE_ERROR=0x6, + REFUSED_STREAM=0x7, + CANCEL=0x8, + COMPRESSION_ERROR=0x9, + CONNECT_ERROR=0xa, + ENHANCE_YOUR_CALM=0xb, + INADEQUATE_SECURITY=0xc, + HTTP_1_1_REQUIRED=0xd + ) + + CLIENT_CONNECTION_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + + HTTP2_DEFAULT_SETTINGS = { + hyperframe.frame.SettingsFrame.HEADER_TABLE_SIZE: 4096, + hyperframe.frame.SettingsFrame.ENABLE_PUSH: 1, + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: None, + hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: 2 ** 16 - 1, + hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE: 2 ** 14, + hyperframe.frame.SettingsFrame.MAX_HEADER_LIST_SIZE: None, + } + + def __init__( + self, + tcp_handler=None, + rfile=None, + wfile=None, + is_server=False, + dump_frames=False, + encoder=None, + decoder=None, + unhandled_frame_cb=None, + ): + self.tcp_handler = tcp_handler or TCPHandler(rfile, wfile) + self.is_server = is_server + self.dump_frames = dump_frames + self.encoder = encoder or Encoder() + self.decoder = decoder or Decoder() + self.unhandled_frame_cb = unhandled_frame_cb + + self.http2_settings = self.HTTP2_DEFAULT_SETTINGS.copy() + self.current_stream_id = None + self.connection_preface_performed = False + + def read_request( + self, + __rfile, + include_body=True, + body_size_limit=None, + allow_empty=False, + ): + if body_size_limit is not None: + raise NotImplementedError() + + self.perform_connection_preface() + + timestamp_start = time.time() + if hasattr(self.tcp_handler.rfile, "reset_timestamps"): + self.tcp_handler.rfile.reset_timestamps() + + stream_id, headers, body = self._receive_transmission( + include_body=include_body, + ) + + if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"): + # more accurate timestamp_start + timestamp_start = self.tcp_handler.rfile.first_byte_timestamp + + timestamp_end = time.time() + + authority = headers.get(':authority', b'') + method = headers.get(':method', 'GET') + scheme = headers.get(':scheme', 'https') + path = headers.get(':path', '/') + + headers.clear(":method") + headers.clear(":scheme") + headers.clear(":path") + + host = None + port = None + + if path == '*' or path.startswith("/"): + first_line_format = "relative" + elif method == 'CONNECT': + first_line_format = "authority" + if ":" in authority: + host, port = authority.split(":", 1) + else: + host = authority + else: + first_line_format = "absolute" + # FIXME: verify if path or :host contains what we need + scheme, host, port, _ = url.parse(path) + scheme = scheme.decode('ascii') + host = host.decode('ascii') + + if host is None: + host = 'localhost' + if port is None: + port = 80 if scheme == 'http' else 443 + port = int(port) + + request = netlib.http.request.Request( + first_line_format, + method.encode('ascii'), + scheme.encode('ascii'), + host.encode('ascii'), + port, + path.encode('ascii'), + b"HTTP/2.0", + headers, + body, + timestamp_start, + timestamp_end, + ) + request.stream_id = stream_id + + return request + + def read_response( + self, + __rfile, + request_method=b'', + body_size_limit=None, + include_body=True, + stream_id=None, + ): + if body_size_limit is not None: + raise NotImplementedError() + + self.perform_connection_preface() + + timestamp_start = time.time() + if hasattr(self.tcp_handler.rfile, "reset_timestamps"): + self.tcp_handler.rfile.reset_timestamps() + + stream_id, headers, body = self._receive_transmission( + stream_id=stream_id, + include_body=include_body, + ) + + if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"): + # more accurate timestamp_start + timestamp_start = self.tcp_handler.rfile.first_byte_timestamp + + if include_body: + timestamp_end = time.time() + else: + timestamp_end = None + + response = netlib.http.response.Response( + b"HTTP/2.0", + int(headers.get(':status', 502)), + b'', + headers, + body, + timestamp_start=timestamp_start, + timestamp_end=timestamp_end, + ) + response.stream_id = stream_id + + return response + + def assemble(self, message): + if isinstance(message, netlib.http.request.Request): + return self.assemble_request(message) + elif isinstance(message, netlib.http.response.Response): + return self.assemble_response(message) + else: + raise ValueError("HTTP message not supported.") + + def assemble_request(self, request): + assert isinstance(request, netlib.http.request.Request) + + authority = self.tcp_handler.sni if self.tcp_handler.sni else self.tcp_handler.address.host + if self.tcp_handler.address.port != 443: + authority += ":%d" % self.tcp_handler.address.port + + headers = request.headers.copy() + + if ':authority' not in headers: + headers.insert(0, b':authority', authority.encode('ascii')) + headers.insert(0, b':scheme', request.scheme.encode('ascii')) + headers.insert(0, b':path', request.path.encode('ascii')) + headers.insert(0, b':method', request.method.encode('ascii')) + + if hasattr(request, 'stream_id'): + stream_id = request.stream_id + else: + stream_id = self._next_stream_id() + + return list(itertools.chain( + self._create_headers(headers, stream_id, end_stream=(request.body is None or len(request.body) == 0)), + self._create_body(request.body, stream_id))) + + def assemble_response(self, response): + assert isinstance(response, netlib.http.response.Response) + + headers = response.headers.copy() + + if ':status' not in headers: + headers.insert(0, b':status', strutils.always_bytes(response.status_code)) + + if hasattr(response, 'stream_id'): + stream_id = response.stream_id + else: + stream_id = self._next_stream_id() + + return list(itertools.chain( + self._create_headers(headers, stream_id, end_stream=(response.body is None or len(response.body) == 0)), + self._create_body(response.body, stream_id), + )) + + def perform_connection_preface(self, force=False): + if force or not self.connection_preface_performed: + if self.is_server: + self.perform_server_connection_preface(force) + else: + self.perform_client_connection_preface(force) + + def perform_server_connection_preface(self, force=False): + if force or not self.connection_preface_performed: + self.connection_preface_performed = True + + magic_length = len(self.CLIENT_CONNECTION_PREFACE) + magic = self.tcp_handler.rfile.safe_read(magic_length) + assert magic == self.CLIENT_CONNECTION_PREFACE + + frm = hyperframe.frame.SettingsFrame(settings={ + hyperframe.frame.SettingsFrame.ENABLE_PUSH: 0, + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1, + }) + self.send_frame(frm, hide=True) + self._receive_settings(hide=True) + + def perform_client_connection_preface(self, force=False): + if force or not self.connection_preface_performed: + self.connection_preface_performed = True + + self.tcp_handler.wfile.write(self.CLIENT_CONNECTION_PREFACE) + + self.send_frame(hyperframe.frame.SettingsFrame(), hide=True) + self._receive_settings(hide=True) # server announces own settings + self._receive_settings(hide=True) # server acks my settings + + def send_frame(self, frm, hide=False): + raw_bytes = frm.serialize() + self.tcp_handler.wfile.write(raw_bytes) + self.tcp_handler.wfile.flush() + if not hide and self.dump_frames: # pragma no cover + print(frm.human_readable(">>")) + + def read_frame(self, hide=False): + while True: + frm = framereader.http2_read_frame(self.tcp_handler.rfile) + if not hide and self.dump_frames: # pragma no cover + print(frm.human_readable("<<")) + + if isinstance(frm, hyperframe.frame.PingFrame): + raw_bytes = hyperframe.frame.PingFrame(flags=['ACK'], payload=frm.payload).serialize() + self.tcp_handler.wfile.write(raw_bytes) + self.tcp_handler.wfile.flush() + continue + if isinstance(frm, hyperframe.frame.SettingsFrame) and 'ACK' not in frm.flags: + self._apply_settings(frm.settings, hide) + if isinstance(frm, hyperframe.frame.DataFrame) and frm.flow_controlled_length > 0: + self._update_flow_control_window(frm.stream_id, frm.flow_controlled_length) + return frm + + def check_alpn(self): + alp = self.tcp_handler.get_alpn_proto_negotiated() + if alp != b'h2': + raise NotImplementedError( + "HTTP2Protocol can not handle unknown ALPN value: %s" % alp) + return True + + def _handle_unexpected_frame(self, frm): + if isinstance(frm, hyperframe.frame.SettingsFrame): + return + if self.unhandled_frame_cb: + self.unhandled_frame_cb(frm) + + def _receive_settings(self, hide=False): + while True: + frm = self.read_frame(hide) + if isinstance(frm, hyperframe.frame.SettingsFrame): + break + else: + self._handle_unexpected_frame(frm) + + def _next_stream_id(self): + if self.current_stream_id is None: + if self.is_server: + # servers must use even stream ids + self.current_stream_id = 2 + else: + # clients must use odd stream ids + self.current_stream_id = 1 + else: + self.current_stream_id += 2 + return self.current_stream_id + + def _apply_settings(self, settings, hide=False): + for setting, value in settings.items(): + old_value = self.http2_settings[setting] + if not old_value: + old_value = '-' + self.http2_settings[setting] = value + + frm = hyperframe.frame.SettingsFrame(flags=['ACK']) + self.send_frame(frm, hide) + + def _update_flow_control_window(self, stream_id, increment): + frm = hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=increment) + self.send_frame(frm) + frm = hyperframe.frame.WindowUpdateFrame(stream_id=stream_id, window_increment=increment) + self.send_frame(frm) + + def _create_headers(self, headers, stream_id, end_stream=True): + def frame_cls(chunks): + for i in chunks: + if i == 0: + yield hyperframe.frame.HeadersFrame, i + else: + yield hyperframe.frame.ContinuationFrame, i + + header_block_fragment = self.encoder.encode(headers.fields) + + chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] + chunks = range(0, len(header_block_fragment), chunk_size) + frms = [frm_cls( + flags=[], + stream_id=stream_id, + data=header_block_fragment[i:i + chunk_size]) for frm_cls, i in frame_cls(chunks)] + + frms[-1].flags.add('END_HEADERS') + if end_stream: + frms[0].flags.add('END_STREAM') + + if self.dump_frames: # pragma no cover + for frm in frms: + print(frm.human_readable(">>")) + + return [frm.serialize() for frm in frms] + + def _create_body(self, body, stream_id): + if body is None or len(body) == 0: + return b'' + + chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] + chunks = range(0, len(body), chunk_size) + frms = [hyperframe.frame.DataFrame( + flags=[], + stream_id=stream_id, + data=body[i:i + chunk_size]) for i in chunks] + frms[-1].flags.add('END_STREAM') + + if self.dump_frames: # pragma no cover + for frm in frms: + print(frm.human_readable(">>")) + + return [frm.serialize() for frm in frms] + + def _receive_transmission(self, stream_id=None, include_body=True): + if not include_body: + raise NotImplementedError() + + body_expected = True + + header_blocks = b'' + body = b'' + + while True: + frm = self.read_frame() + if ( + (isinstance(frm, hyperframe.frame.HeadersFrame) or isinstance(frm, hyperframe.frame.ContinuationFrame)) and + (stream_id is None or frm.stream_id == stream_id) + ): + stream_id = frm.stream_id + header_blocks += frm.data + if 'END_STREAM' in frm.flags: + body_expected = False + if 'END_HEADERS' in frm.flags: + break + else: + self._handle_unexpected_frame(frm) + + while body_expected: + frm = self.read_frame() + if isinstance(frm, hyperframe.frame.DataFrame) and frm.stream_id == stream_id: + body += frm.data + if 'END_STREAM' in frm.flags: + break + else: + self._handle_unexpected_frame(frm) + + headers = netlib.http.headers.Headers( + (k.encode('ascii'), v.encode('ascii')) for k, v in self.decoder.decode(header_blocks) + ) + + return stream_id, headers, body + + +class HTTP2Protocol(object): def __init__(self, pathod_handler): self.pathod_handler = pathod_handler - self.wire_protocol = http2.HTTP2Protocol( + self.wire_protocol = HTTP2StateProtocol( self.pathod_handler, is_server=True, dump_frames=self.pathod_handler.http2_framedump ) diff --git a/test/netlib/http/http2/test_framereader.py b/test/netlib/http/http2/test_framereader.py new file mode 100644 index 00000000..41b73189 --- /dev/null +++ b/test/netlib/http/http2/test_framereader.py @@ -0,0 +1 @@ +# foobar diff --git a/test/pathod/__init__.py b/test/pathod/__init__.py new file mode 100644 index 00000000..3f5dc124 --- /dev/null +++ b/test/pathod/__init__.py @@ -0,0 +1 @@ +from __future__ import (print_function, absolute_import, division) diff --git a/test/pathod/test_language_actions.py b/test/pathod/test_language_actions.py index f12d8105..2b1b6915 100644 --- a/test/pathod/test_language_actions.py +++ b/test/pathod/test_language_actions.py @@ -1,11 +1,10 @@ from six import BytesIO -from pathod.language import actions -from pathod import language +from pathod.language import actions, parse_pathoc, parse_pathod, serve def parse_request(s): - return next(language.parse_pathoc(s)) + return next(parse_pathoc(s)) def test_unique_name(): @@ -16,9 +15,9 @@ def test_unique_name(): class TestDisconnects: def test_parse_pathod(self): - a = next(language.parse_pathod("400:d0")).actions[0] + a = next(parse_pathod("400:d0")).actions[0] assert a.spec() == "d0" - a = next(language.parse_pathod("400:dr")).actions[0] + a = next(parse_pathod("400:dr")).actions[0] assert a.spec() == "dr" def test_at(self): @@ -42,12 +41,12 @@ class TestDisconnects: class TestInject: def test_parse_pathod(self): - a = next(language.parse_pathod("400:ir,@100")).actions[0] + a = next(parse_pathod("400:ir,@100")).actions[0] assert a.offset == "r" assert a.value.datatype == "bytes" assert a.value.usize == 100 - a = next(language.parse_pathod("400:ia,@100")).actions[0] + a = next(parse_pathod("400:ia,@100")).actions[0] assert a.offset == "a" def test_at(self): @@ -62,8 +61,8 @@ class TestInject: def test_serve(self): s = BytesIO() - r = next(language.parse_pathod("400:i0,'foo'")) - assert language.serve(r, s, {}) + r = next(parse_pathod("400:i0,'foo'")) + assert serve(r, s, {}) def test_spec(self): e = actions.InjectAt.expr() @@ -96,7 +95,7 @@ class TestPauses: assert v.offset == "a" def test_request(self): - r = next(language.parse_pathod('400:p10,10')) + r = next(parse_pathod('400:p10,10')) assert r.actions[0].spec() == "p10,10" def test_spec(self): diff --git a/test/pathod/test_language_base.py b/test/pathod/test_language_base.py index 7c7d8cf9..12a235e4 100644 --- a/test/pathod/test_language_base.py +++ b/test/pathod/test_language_base.py @@ -1,7 +1,8 @@ import os from pathod import language from pathod.language import base, exceptions -import tutils + +from . import tutils def parse_request(s): diff --git a/test/pathod/test_language_generators.py b/test/pathod/test_language_generators.py index 51f55991..4ec6ec3f 100644 --- a/test/pathod/test_language_generators.py +++ b/test/pathod/test_language_generators.py @@ -1,7 +1,7 @@ import os from pathod.language import generators -import tutils +from . import tutils def test_randomgenerator(): diff --git a/test/pathod/test_language_http.py b/test/pathod/test_language_http.py index 18059e3a..dd0b8d02 100644 --- a/test/pathod/test_language_http.py +++ b/test/pathod/test_language_http.py @@ -1,7 +1,8 @@ from six import BytesIO from pathod import language from pathod.language import http, base -import tutils + +from . import tutils def parse_request(s): diff --git a/test/pathod/test_language_http2.py b/test/pathod/test_language_http2.py index a2bffe63..f4b34047 100644 --- a/test/pathod/test_language_http2.py +++ b/test/pathod/test_language_http2.py @@ -1,12 +1,13 @@ from six import BytesIO -import netlib from netlib import tcp from netlib.http import user_agents from pathod import language from pathod.language import http2 -import tutils +from pathod.protocols.http2 import HTTP2StateProtocol + +from . import tutils def parse_request(s): @@ -20,7 +21,7 @@ def parse_response(s): def default_settings(): return language.Settings( request_host="foo.com", - protocol=netlib.http.http2.HTTP2Protocol(tcp.TCPClient(('localhost', 1234))) + protocol=HTTP2StateProtocol(tcp.TCPClient(('localhost', 1234))) ) diff --git a/test/pathod/test_language_websocket.py b/test/pathod/test_language_websocket.py index 58297141..89cbb772 100644 --- a/test/pathod/test_language_websocket.py +++ b/test/pathod/test_language_websocket.py @@ -2,7 +2,8 @@ from pathod import language from pathod.language import websockets import netlib.websockets -import tutils + +from . import tutils def parse_request(s): diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py index 05cf518d..28f9f0f8 100644 --- a/test/pathod/test_pathoc.py +++ b/test/pathod/test_pathoc.py @@ -5,11 +5,13 @@ from mock import Mock from netlib import http from netlib import tcp from netlib.exceptions import NetlibException -from netlib.http import http1, http2 +from netlib.http import http1 +from netlib.tutils import raises from pathod import pathoc, language -from netlib.tutils import raises -import tutils +from pathod.protocols.http2 import HTTP2StateProtocol + +from . import tutils def test_response(): @@ -169,7 +171,7 @@ class TestDaemon(PathocTestDaemon): def test_connect_fail(self): to = ("foobar", 80) c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) - c.rfile, c.wfile = StringIO(), StringIO() + c.rfile, c.wfile = BytesIO(), BytesIO() with raises("connect failed"): c.http_connect(to) c.rfile = BytesIO( @@ -219,7 +221,7 @@ class TestDaemonHTTP2(PathocTestDaemon): ssl=True, use_http2=True, ) - assert isinstance(c.protocol, http2.HTTP2Protocol) + assert isinstance(c.protocol, HTTP2StateProtocol) c = pathoc.Pathoc( ("127.0.0.1", self.d.port), diff --git a/test/pathod/test_pathoc_cmdline.py b/test/pathod/test_pathoc_cmdline.py index 35909325..922cf3a9 100644 --- a/test/pathod/test_pathoc_cmdline.py +++ b/test/pathod/test_pathoc_cmdline.py @@ -1,8 +1,10 @@ -from pathod import pathoc_cmdline as cmdline -import tutils from six.moves import cStringIO as StringIO import mock +from pathod import pathoc_cmdline as cmdline + +from . import tutils + @mock.patch("argparse.ArgumentParser.error") def test_pathoc(perror): diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py index ed4ef49f..0b34f924 100644 --- a/test/pathod/test_pathod.py +++ b/test/pathod/test_pathod.py @@ -3,7 +3,8 @@ from six.moves import cStringIO as StringIO from pathod import pathod from netlib import tcp from netlib.exceptions import HttpException, TlsException -import tutils + +from . import tutils class TestPathod(object): @@ -52,7 +53,7 @@ class TestNotAfterConnect(tutils.DaemonTests): class TestCustomCert(tutils.DaemonTests): ssl = True ssloptions = dict( - certs=[("*", tutils.test_data.path("data/testkey.pem"))], + certs=[(b"*", tutils.test_data.path("data/testkey.pem"))], ) def test_connect(self): @@ -66,7 +67,7 @@ class TestCustomCert(tutils.DaemonTests): class TestSSLCN(tutils.DaemonTests): ssl = True ssloptions = dict( - cn="foo.com" + cn=b"foo.com" ) def test_connect(self): @@ -100,7 +101,7 @@ class TestNocraft(tutils.DaemonTests): def test_nocraft(self): r = self.get(r"200:b'\xf0'") assert r.status_code == 800 - assert "Crafting disabled" in r.content + assert b"Crafting disabled" in r.content class CommonTests(tutils.DaemonTests): @@ -137,7 +138,7 @@ class CommonTests(tutils.DaemonTests): def test_static(self): rsp = self.get("200:b<file") assert rsp.status_code == 200 - assert rsp.content.strip() == "testfile" + assert rsp.content.strip() == b"testfile" def test_anchor(self): rsp = self.getpath("/anchor/foo") @@ -148,7 +149,7 @@ class CommonTests(tutils.DaemonTests): with c.connect(): if self.ssl: c.convert_to_ssl() - c.wfile.write("foo\n\n\n") + c.wfile.write(b"foo\n\n\n") c.wfile.flush() l = self.d.last_log() assert l["type"] == "error" @@ -177,7 +178,7 @@ class CommonTests(tutils.DaemonTests): def test_source_access_denied(self): rsp = self.get("200:b</foo") assert rsp.status_code == 800 - assert "File access denied" in rsp.content + assert b"File access denied" in rsp.content def test_proxy(self): r, _ = self.pathoc([r"get:'http://foo.com/p/202':da"]) @@ -195,7 +196,7 @@ class CommonTests(tutils.DaemonTests): ["ws:/p/", "wf:f'wf:b\"test\"':pa,1"], ws_read_limit=1 ) - assert r[1].payload == "test" + assert r[1].payload == b"test" def test_websocket_frame_reflect_error(self): r, _ = self.pathoc( @@ -239,7 +240,7 @@ class TestDaemonSSL(CommonTests): c.rbufsize = 0 c.wbufsize = 0 with c.connect(): - c.wfile.write("\0\0\0\0") + c.wfile.write(b"\0\0\0\0") tutils.raises(TlsException, c.convert_to_ssl) l = self.d.last_log() assert l["type"] == "error" diff --git a/test/pathod/test_pathod_cmdline.py b/test/pathod/test_pathod_cmdline.py index 18d54c82..58123b37 100644 --- a/test/pathod/test_pathod_cmdline.py +++ b/test/pathod/test_pathod_cmdline.py @@ -1,7 +1,9 @@ -from pathod import pathod_cmdline as cmdline -import tutils import mock +from pathod import pathod_cmdline as cmdline + +from . import tutils + def test_parse_anchor_spec(): assert cmdline.parse_anchor_spec("foo=200") == ("foo", "200") diff --git a/test/netlib/http/http2/test_connections.py b/test/pathod/test_protocols_http2.py index 2a43627a..e42c2858 100644 --- a/test/netlib/http/http2/test_connections.py +++ b/test/pathod/test_protocols_http2.py @@ -5,21 +5,22 @@ import hyperframe from netlib import tcp, http from netlib.tutils import raises from netlib.exceptions import TcpDisconnect -from netlib.http.http2.connections import HTTP2Protocol, TCPHandler from netlib.http.http2 import framereader -from ... import tservers +from ..netlib import tservers as netlib_tservers + +from pathod.protocols.http2 import HTTP2StateProtocol, TCPHandler class TestTCPHandlerWrapper: def test_wrapped(self): h = TCPHandler(rfile='foo', wfile='bar') - p = HTTP2Protocol(h) + p = HTTP2StateProtocol(h) assert p.tcp_handler.rfile == 'foo' assert p.tcp_handler.wfile == 'bar' def test_direct(self): - p = HTTP2Protocol(rfile='foo', wfile='bar') + p = HTTP2StateProtocol(rfile='foo', wfile='bar') assert isinstance(p.tcp_handler, TCPHandler) assert p.tcp_handler.rfile == 'foo' assert p.tcp_handler.wfile == 'bar' @@ -36,10 +37,10 @@ class EchoHandler(tcp.BaseHandler): class TestProtocol: - @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_server_connection_preface") - @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_client_connection_preface") + @mock.patch("pathod.protocols.http2.HTTP2StateProtocol.perform_server_connection_preface") + @mock.patch("pathod.protocols.http2.HTTP2StateProtocol.perform_client_connection_preface") def test_perform_connection_preface(self, mock_client_method, mock_server_method): - protocol = HTTP2Protocol(is_server=False) + protocol = HTTP2StateProtocol(is_server=False) protocol.connection_preface_performed = True protocol.perform_connection_preface() @@ -50,10 +51,10 @@ class TestProtocol: assert mock_client_method.called assert not mock_server_method.called - @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_server_connection_preface") - @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_client_connection_preface") + @mock.patch("pathod.protocols.http2.HTTP2StateProtocol.perform_server_connection_preface") + @mock.patch("pathod.protocols.http2.HTTP2StateProtocol.perform_client_connection_preface") def test_perform_connection_preface_server(self, mock_client_method, mock_server_method): - protocol = HTTP2Protocol(is_server=True) + protocol = HTTP2StateProtocol(is_server=True) protocol.connection_preface_performed = True protocol.perform_connection_preface() @@ -65,7 +66,7 @@ class TestProtocol: assert mock_server_method.called -class TestCheckALPNMatch(tservers.ServerTestBase): +class TestCheckALPNMatch(netlib_tservers.ServerTestBase): handler = EchoHandler ssl = dict( alpn_select=b'h2', @@ -77,11 +78,11 @@ class TestCheckALPNMatch(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl(alpn_protos=[b'h2']) - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) assert protocol.check_alpn() -class TestCheckALPNMismatch(tservers.ServerTestBase): +class TestCheckALPNMismatch(netlib_tservers.ServerTestBase): handler = EchoHandler ssl = dict( alpn_select=None, @@ -93,12 +94,12 @@ class TestCheckALPNMismatch(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl(alpn_protos=[b'h2']) - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) with raises(NotImplementedError): protocol.check_alpn() -class TestPerformServerConnectionPreface(tservers.ServerTestBase): +class TestPerformServerConnectionPreface(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): @@ -125,7 +126,7 @@ class TestPerformServerConnectionPreface(tservers.ServerTestBase): def test_perform_server_connection_preface(self): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) assert not protocol.connection_preface_performed protocol.perform_server_connection_preface() @@ -135,12 +136,12 @@ class TestPerformServerConnectionPreface(tservers.ServerTestBase): protocol.perform_server_connection_preface(force=True) -class TestPerformClientConnectionPreface(tservers.ServerTestBase): +class TestPerformClientConnectionPreface(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): # check magic - assert self.rfile.read(24) == HTTP2Protocol.CLIENT_CONNECTION_PREFACE + assert self.rfile.read(24) == HTTP2StateProtocol.CLIENT_CONNECTION_PREFACE # check empty settings frame assert self.rfile.read(9) ==\ @@ -161,7 +162,7 @@ class TestPerformClientConnectionPreface(tservers.ServerTestBase): def test_perform_client_connection_preface(self): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) assert not protocol.connection_preface_performed protocol.perform_client_connection_preface() @@ -170,7 +171,7 @@ class TestPerformClientConnectionPreface(tservers.ServerTestBase): class TestClientStreamIds(object): c = tcp.TCPClient(("127.0.0.1", 0)) - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) def test_client_stream_ids(self): assert self.protocol.current_stream_id is None @@ -182,9 +183,9 @@ class TestClientStreamIds(object): assert self.protocol.current_stream_id == 5 -class TestServerStreamIds(object): +class TestserverstreamIds(object): c = tcp.TCPClient(("127.0.0.1", 0)) - protocol = HTTP2Protocol(c, is_server=True) + protocol = HTTP2StateProtocol(c, is_server=True) def test_server_stream_ids(self): assert self.protocol.current_stream_id is None @@ -196,7 +197,7 @@ class TestServerStreamIds(object): assert self.protocol.current_stream_id == 6 -class TestApplySettings(tservers.ServerTestBase): +class TestApplySettings(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): # check settings acknowledgement @@ -211,7 +212,7 @@ class TestApplySettings(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) protocol._apply_settings({ hyperframe.frame.SettingsFrame.ENABLE_PUSH: 'foo', @@ -239,12 +240,12 @@ class TestCreateHeaders(object): (b':scheme', b'https'), (b'foo', b'bar')]) - bytes = HTTP2Protocol(self.c)._create_headers( + bytes = HTTP2StateProtocol(self.c)._create_headers( headers, 1, end_stream=True) assert b''.join(bytes) ==\ codecs.decode('000014010500000001824488355217caf3a69a3f87408294e7838c767f', 'hex_codec') - bytes = HTTP2Protocol(self.c)._create_headers( + bytes = HTTP2StateProtocol(self.c)._create_headers( headers, 1, end_stream=False) assert b''.join(bytes) ==\ codecs.decode('000014010400000001824488355217caf3a69a3f87408294e7838c767f', 'hex_codec') @@ -257,7 +258,7 @@ class TestCreateHeaders(object): (b'foo', b'bar'), (b'server', b'version')]) - protocol = HTTP2Protocol(self.c) + protocol = HTTP2StateProtocol(self.c) protocol.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] = 8 bytes = protocol._create_headers(headers, 1, end_stream=True) assert len(bytes) == 3 @@ -270,17 +271,17 @@ class TestCreateBody(object): c = tcp.TCPClient(("127.0.0.1", 0)) def test_create_body_empty(self): - protocol = HTTP2Protocol(self.c) + protocol = HTTP2StateProtocol(self.c) bytes = protocol._create_body(b'', 1) assert b''.join(bytes) == b'' def test_create_body_single_frame(self): - protocol = HTTP2Protocol(self.c) + protocol = HTTP2StateProtocol(self.c) bytes = protocol._create_body(b'foobar', 1) assert b''.join(bytes) == codecs.decode('000006000100000001666f6f626172', 'hex_codec') def test_create_body_multiple_frames(self): - protocol = HTTP2Protocol(self.c) + protocol = HTTP2StateProtocol(self.c) protocol.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] = 5 bytes = protocol._create_body(b'foobarmehm42', 1) assert len(bytes) == 3 @@ -289,7 +290,7 @@ class TestCreateBody(object): assert bytes[2] == codecs.decode('0000020001000000013432', 'hex_codec') -class TestReadRequest(tservers.ServerTestBase): +class TestReadRequest(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): @@ -306,7 +307,7 @@ class TestReadRequest(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c, is_server=True) + protocol = HTTP2StateProtocol(c, is_server=True) protocol.connection_preface_performed = True req = protocol.read_request(NotImplemented) @@ -319,7 +320,7 @@ class TestReadRequest(tservers.ServerTestBase): assert req.content == b'foobar' -class TestReadRequestRelative(tservers.ServerTestBase): +class TestReadRequestRelative(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): self.wfile.write( @@ -332,7 +333,7 @@ class TestReadRequestRelative(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c, is_server=True) + protocol = HTTP2StateProtocol(c, is_server=True) protocol.connection_preface_performed = True req = protocol.read_request(NotImplemented) @@ -342,7 +343,7 @@ class TestReadRequestRelative(tservers.ServerTestBase): assert req.path == "*" -class TestReadRequestAbsolute(tservers.ServerTestBase): +class TestReadRequestAbsolute(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): self.wfile.write( @@ -355,7 +356,7 @@ class TestReadRequestAbsolute(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c, is_server=True) + protocol = HTTP2StateProtocol(c, is_server=True) protocol.connection_preface_performed = True req = protocol.read_request(NotImplemented) @@ -366,7 +367,7 @@ class TestReadRequestAbsolute(tservers.ServerTestBase): assert req.port == 22 -class TestReadRequestConnect(tservers.ServerTestBase): +class TestReadRequestConnect(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): self.wfile.write( @@ -381,7 +382,7 @@ class TestReadRequestConnect(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c, is_server=True) + protocol = HTTP2StateProtocol(c, is_server=True) protocol.connection_preface_performed = True req = protocol.read_request(NotImplemented) @@ -397,7 +398,7 @@ class TestReadRequestConnect(tservers.ServerTestBase): assert req.port == 443 -class TestReadResponse(tservers.ServerTestBase): +class TestReadResponse(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): self.wfile.write( @@ -413,7 +414,7 @@ class TestReadResponse(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) protocol.connection_preface_performed = True resp = protocol.read_response(NotImplemented, stream_id=42) @@ -426,7 +427,7 @@ class TestReadResponse(tservers.ServerTestBase): assert resp.timestamp_end -class TestReadEmptyResponse(tservers.ServerTestBase): +class TestReadEmptyResponse(netlib_tservers.ServerTestBase): class handler(tcp.BaseHandler): def handle(self): self.wfile.write( @@ -439,7 +440,7 @@ class TestReadEmptyResponse(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): c.convert_to_ssl() - protocol = HTTP2Protocol(c) + protocol = HTTP2StateProtocol(c) protocol.connection_preface_performed = True resp = protocol.read_response(NotImplemented, stream_id=42) @@ -456,7 +457,7 @@ class TestAssembleRequest(object): c = tcp.TCPClient(("127.0.0.1", 0)) def test_request_simple(self): - bytes = HTTP2Protocol(self.c).assemble_request(http.Request( + bytes = HTTP2StateProtocol(self.c).assemble_request(http.Request( b'', b'GET', b'https', @@ -483,12 +484,12 @@ class TestAssembleRequest(object): None, ) req.stream_id = 0x42 - bytes = HTTP2Protocol(self.c).assemble_request(req) + bytes = HTTP2StateProtocol(self.c).assemble_request(req) assert len(bytes) == 1 assert bytes[0] == codecs.decode('00000d0105000000428284874188089d5c0b8170dc07', 'hex_codec') def test_request_with_body(self): - bytes = HTTP2Protocol(self.c).assemble_request(http.Request( + bytes = HTTP2StateProtocol(self.c).assemble_request(http.Request( b'', b'GET', b'https', @@ -510,7 +511,7 @@ class TestAssembleResponse(object): c = tcp.TCPClient(("127.0.0.1", 0)) def test_simple(self): - bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(http.Response( + bytes = HTTP2StateProtocol(self.c, is_server=True).assemble_response(http.Response( b"HTTP/2.0", 200, )) @@ -524,13 +525,13 @@ class TestAssembleResponse(object): 200, ) resp.stream_id = 0x42 - bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(resp) + bytes = HTTP2StateProtocol(self.c, is_server=True).assemble_response(resp) assert len(bytes) == 1 assert bytes[0] ==\ codecs.decode('00000101050000004288', 'hex_codec') def test_with_body(self): - bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(http.Response( + bytes = HTTP2StateProtocol(self.c, is_server=True).assemble_response(http.Response( b"HTTP/2.0", 200, b'', diff --git a/test/pathod/test_test.py b/test/pathod/test_test.py index 6399894e..d69e72f3 100644 --- a/test/pathod/test_test.py +++ b/test/pathod/test_test.py @@ -1,7 +1,8 @@ import logging import requests from pathod import test -import tutils + +from . import tutils import requests.packages.urllib3 diff --git a/test/pathod/test_utils.py b/test/pathod/test_utils.py index 2bb82fe7..2bdfe501 100644 --- a/test/pathod/test_utils.py +++ b/test/pathod/test_utils.py @@ -1,5 +1,6 @@ from pathod import utils -import tutils + +from . import tutils def test_membool(): diff --git a/test/pathod/tutils.py b/test/pathod/tutils.py index daaa8628..3a94b6eb 100644 --- a/test/pathod/tutils.py +++ b/test/pathod/tutils.py @@ -100,7 +100,7 @@ class DaemonTests(object): ) with c.connect(): resp = c.request( - "get:/p/%s" % urllib.parse.quote(spec).encode("string_escape") + "get:/p/%s" % urllib.parse.quote(spec) ) return resp @@ -7,7 +7,7 @@ deps = codecov>=2.0.5 passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT setenv = - PY3TESTS = test/netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py test/pathod/test_language_websocket.py test/pathod/test_language_http2.py test/pathod/test_pathoc.py + PY3TESTS = test/netlib test/pathod/ test/mitmproxy/script [testenv:py27] commands = diff --git a/web/.editorconfig b/web/.editorconfig index 9acd1b0f..ddc78021 100644 --- a/web/.editorconfig +++ b/web/.editorconfig @@ -3,3 +3,4 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true +end_of_line = lf diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less index 908312cd..393f75db 100644 --- a/web/src/css/eventlog.less +++ b/web/src/css/eventlog.less @@ -10,6 +10,8 @@ background-color: #F2F2F2; padding: 0 5px; flex: 0 0 auto; + border-top: 1px solid #aaa; + cursor: row-resize; } > pre { @@ -48,4 +50,4 @@ margin-top: -2px; margin-left: 3px; } -}
\ No newline at end of file +} diff --git a/web/src/js/components/ContentView.jsx b/web/src/js/components/ContentView.jsx new file mode 100644 index 00000000..af3bffc1 --- /dev/null +++ b/web/src/js/components/ContentView.jsx @@ -0,0 +1,78 @@ +import React, { Component, PropTypes } from 'react' +import { MessageUtils } from '../flow/utils.js' +import { ViewAuto, ViewImage } from './ContentView/ContentViews' +import * as ContentErrors from './ContentView/ContentErrors' +import ContentLoader from './ContentView/ContentLoader' +import ViewSelector from './ContentView/ViewSelector' + +export default class ContentView extends Component { + + static propTypes = { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // <Auto flow={flow} message={flow.request}/> + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + + this.state = { displayLarge: false, View: ViewAuto } + this.selectView = this.selectView.bind(this) + } + + selectView(View) { + this.setState({ View }) + } + + displayLarge() { + this.setState({ displayLarge: true }) + } + + componentWillReceiveProps(nextProps) { + if (nextProps.message !== this.props.message) { + this.setState({ displayLarge: false, View: ViewAuto }) + } + } + + isContentTooLarge(msg) { + return msg.contentLength > 1024 * 1024 * (ViewImage.matches(msg) ? 10 : 0.2) + } + + render() { + const { flow, message } = this.props + const { displayLarge, View } = this.state + + if (message.contentLength === 0) { + return <ContentErrors.ContentEmpty {...this.props}/> + } + + if (message.contentLength === null) { + return <ContentErrors.ContentMissing {...this.props}/> + } + + if (!displayLarge && this.isContentTooLarge(message)) { + return <ContentErrors.ContentTooLarge {...this.props} onClick={this.displayLarge}/> + } + + return ( + <div> + {View.textView ? ( + <ContentLoader flow={flow} message={message}> + <this.state.View content="" /> + </ContentLoader> + ) : ( + <View flow={flow} message={message} /> + )} + <div className="view-options text-center"> + <ViewSelector onSelectView={this.selectView} active={View} message={message}/> + + <a className="btn btn-default btn-xs" href={MessageUtils.getContentURL(flow, message)}> + <i className="fa fa-download"/> + </a> + </div> + </div> + ) + } +} diff --git a/web/src/js/components/ContentView/ContentErrors.jsx b/web/src/js/components/ContentView/ContentErrors.jsx new file mode 100644 index 00000000..11594c7f --- /dev/null +++ b/web/src/js/components/ContentView/ContentErrors.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import { ViewImage } from './ContentViews' +import {formatSize} from '../../utils.js' + +export function ContentEmpty({ flow, message }) { + return ( + <div className="alert alert-info"> + No {flow.request === message ? 'request' : 'response'} content. + </div> + ) +} + +export function ContentMissing({ flow, message }) { + return ( + <div className="alert alert-info"> + {flow.request === message ? 'Request' : 'Response'} content missing. + </div> + ) +} + +export function ContentTooLarge({ message, onClick }) { + return ( + <div className="alert alert-warning"> + <button onClick={onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button> + {formatSize(message.contentLength)} content size. + </div> + ) +} diff --git a/web/src/js/components/ContentView/ContentLoader.jsx b/web/src/js/components/ContentView/ContentLoader.jsx new file mode 100644 index 00000000..f346dc01 --- /dev/null +++ b/web/src/js/components/ContentView/ContentLoader.jsx @@ -0,0 +1,67 @@ +import React, { Component, PropTypes } from 'react' +import { MessageUtils } from '../../flow/utils.js' + +export default class ContentLoader extends Component { + + static propTypes = { + flow: PropTypes.object.isRequired, + message: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + this.state = { content: null, request: null } + } + + requestContent(nextProps) { + if (this.state.request) { + this.state.request.abort() + } + + const request = MessageUtils.getContent(nextProps.flow, nextProps.message) + + this.setState({ content: null, request }) + + request + .done(content => { + this.setState({ content }) + }) + .fail((xhr, textStatus, errorThrown) => { + if (textStatus === 'abort') { + return + } + this.setState({ content: `AJAX Error: ${textStatus}\r\n${errorThrown}` }) + }) + .always(() => { + this.setState({ request: null }) + }) + } + + componentWillMount() { + this.requestContent(this.props) + } + + componentWillReceiveProps(nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps) + } + } + + componentWillUnmount() { + if (this.state.request) { + this.state.request.abort() + } + } + + render() { + return this.state.content ? ( + React.cloneElement(this.props.children, { + content: this.state.content + }) + ) : ( + <div className="text-center"> + <i className="fa fa-spinner fa-spin"></i> + </div> + ) + } +} diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx new file mode 100644 index 00000000..b0297dcc --- /dev/null +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -0,0 +1,70 @@ +import React, { PropTypes } from 'react' +import ContentLoader from './ContentLoader' +import { MessageUtils } from '../../flow/utils.js' + +const views = [ViewAuto, ViewImage, ViewJSON, ViewRaw] + +ViewImage.regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i +ViewImage.matches = msg => ViewImage.regex.test(MessageUtils.getContentType(msg)) + +ViewImage.propTypes = { + flow: PropTypes.object.isRequired, + message: PropTypes.object.isRequired, +} + +export function ViewImage({ flow, message }) { + return ( + <div className="flowview-image"> + <img src={MessageUtils.getContentURL(flow, message)} alt="preview" className="img-thumbnail"/> + </div> + ) +} + +ViewRaw.textView = true +ViewRaw.matches = () => true + +ViewRaw.propTypes = { + content: React.PropTypes.string.isRequired, +} + +export function ViewRaw({ content }) { + return <pre>{content}</pre> +} + +ViewJSON.textView = true +ViewJSON.regex = /^application\/json$/i +ViewJSON.matches = msg => ViewJSON.regex.test(MessageUtils.getContentType(msg)) + +ViewJSON.propTypes = { + content: React.PropTypes.string.isRequired, +} + +export function ViewJSON({ content }) { + let json = content + try { + json = JSON.stringify(JSON.parse(content), null, 2); + } catch (e) { + // @noop + } + return <pre>{json}</pre> +} + + +ViewAuto.matches = () => false +ViewAuto.findView = msg => views.find(v => v.matches(msg)) || views[views.length - 1] + +ViewAuto.propTypes = { + message: React.PropTypes.object.isRequired, + flow: React.PropTypes.object.isRequired, +} + +export function ViewAuto({ message, flow }) { + const View = ViewAuto.findView(message) + if (View.textView) { + return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader> + } else { + return <View message={message} flow={flow} /> + } +} + +export default views diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx new file mode 100644 index 00000000..df3a5b83 --- /dev/null +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -0,0 +1,28 @@ +import React, { PropTypes } from 'react' +import classnames from 'classnames' +import views, { ViewAuto } from './ContentViews' + +ViewSelector.propTypes = { + active: PropTypes.func.isRequired, + message: PropTypes.object.isRequired, + onSelectView: PropTypes.func.isRequired, +} + +export default function ViewSelector({ active, message, onSelectView }) { + return ( + <div className="view-selector btn-group btn-group-xs"> + {views.map(View => ( + <button + key={View.name} + onClick={() => onSelectView(View)} + className={classnames('btn btn-default', { active: View === active })}> + {View === ViewAuto ? ( + `auto: ${ViewAuto.findView(message).name.toLowerCase().replace('view', '')}` + ) : ( + View.name.toLowerCase().replace('view', '') + )} + </button> + ))} + </div> + ) +} diff --git a/web/src/js/components/EventLog.jsx b/web/src/js/components/EventLog.jsx index 58cecd1a..169162ee 100644 --- a/web/src/js/components/EventLog.jsx +++ b/web/src/js/components/EventLog.jsx @@ -1,31 +1,70 @@ -import React, { PropTypes } from 'react' +import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import { toggleEventLogFilter, toggleEventLogVisibility } from '../ducks/eventLog' -import { ToggleButton } from './common' +import ToggleButton from './common/ToggleButton' import EventList from './EventLog/EventList' -EventLog.propTypes = { - filters: PropTypes.object.isRequired, - events: PropTypes.array.isRequired, - onToggleFilter: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired -} +class EventLog extends Component { + + static propTypes = { + filters: PropTypes.object.isRequired, + events: PropTypes.array.isRequired, + onToggleFilter: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + defaultHeight: PropTypes.number, + } + + static defaultProps = { + defaultHeight: 200, + } + + constructor(props, context) { + super(props, context) + + this.state = { height: this.props.defaultHeight } + + this.onDragStart = this.onDragStart.bind(this) + this.onDragMove = this.onDragMove.bind(this) + this.onDragStop = this.onDragStop.bind(this) + } -function EventLog({ filters, events, onToggleFilter, onClose }) { - return ( - <div className="eventlog"> - <div> - Eventlog - <div className="pull-right"> - {['debug', 'info', 'web'].map(type => ( - <ToggleButton key={type} text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/> - ))} - <i onClick={onClose} className="fa fa-close"></i> + onDragStart(event) { + event.preventDefault() + this.dragStart = this.state.height + event.pageY + window.addEventListener('mousemove', this.onDragMove) + window.addEventListener('mouseup', this.onDragStop) + window.addEventListener('dragend', this.onDragStop) + } + + onDragMove(event) { + event.preventDefault() + this.setState({ height: this.dragStart - event.pageY }) + } + + onDragStop(event) { + event.preventDefault() + window.removeEventListener('mousemove', this.onDragMove) + } + + render() { + const { height } = this.state + const { filters, events, onToggleFilter, onClose } = this.props + + return ( + <div className="eventlog" style={{ height }}> + <div onMouseDown={this.onDragStart}> + Eventlog + <div className="pull-right"> + {['debug', 'info', 'web'].map(type => ( + <ToggleButton key={type} text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/> + ))} + <i onClick={onClose} className="fa fa-close"></i> + </div> </div> + <EventList events={events} /> </div> - <EventList events={events} /> - </div> - ) + ) + } } export default connect( diff --git a/web/src/js/components/FlowTable/FlowTableHead.jsx b/web/src/js/components/FlowTable/FlowTableHead.jsx index 1df38aba..840f6a34 100644 --- a/web/src/js/components/FlowTable/FlowTableHead.jsx +++ b/web/src/js/components/FlowTable/FlowTableHead.jsx @@ -19,16 +19,12 @@ function FlowTableHead({ sortColumn, sortDesc, onSort }) { {columns.map(Column => ( <th className={classnames(Column.headerClass, sortColumn === Column.name && sortType)} key={Column.name} - onClick={() => onClick(Column)}> + onClick={() => onSort({ sortColumn: Column.name, sortDesc: Column.name !== sortColumn ? false : !sortDesc })}> {Column.headerName} </th> ))} </tr> ) - - function onClick(Column) { - onSort({ sortColumn: Column.name, sortDesc: Column.name !== sortColumn ? false : !sortDesc }) - } } export default connect( diff --git a/web/src/js/components/FlowView.jsx b/web/src/js/components/FlowView.jsx new file mode 100644 index 00000000..23f8b3ea --- /dev/null +++ b/web/src/js/components/FlowView.jsx @@ -0,0 +1,107 @@ +import React, { Component } from 'react' +import _ from 'lodash' + +import Nav from './FlowView/Nav' +import { Request, Response, Error } from './FlowView/Messages' +import Details from './FlowView/Details' +import Prompt from './Prompt' + +export default class FlowView extends Component { + + static allTabs = { Request, Response, Error, Details } + + constructor(props, context) { + super(props, context) + + this.state = { prompt: false } + + this.closePrompt = this.closePrompt.bind(this) + this.selectTab = this.selectTab.bind(this) + } + + getTabs() { + return ['request', 'response', 'error'].filter(k => this.props.flow[k]).concat(['details']) + } + + nextTab(increment) { + const tabs = this.getTabs() + // JS modulo operator doesn't correct negative numbers, make sure that we are positive. + this.selectTab(tabs[(tabs.indexOf(this.props.tab) + increment + tabs.length) % tabs.length]) + } + + selectTab(panel) { + this.props.updateLocation(`/flows/${this.props.flow.id}/${panel}`) + } + + closePrompt(edit) { + this.setState({ prompt: false }) + if (edit) { + this.refs.tab.edit(edit) + } + } + + promptEdit() { + let options + + switch (this.props.tab) { + + case 'request': + options = [ + 'method', + 'url', + { text: 'http version', key: 'v' }, + 'header' + ] + break + + case 'response': + options = [ + { text: 'http version', key: 'v' }, + 'code', + 'message', + 'header' + ] + break + + case 'details': + return + + default: + throw 'Unknown tab for edit: ' + this.props.tab + } + + this.setState({ prompt: { options, done: this.closePrompt } }) + } + + render() { + const tabs = this.getTabs() + let { flow, tab: active } = this.props + + if (tabs.indexOf(active) < 0) { + if (active === 'response' && flow.error) { + active = 'error' + } else if (active === 'error' && flow.response) { + active = 'response' + } else { + active = tabs[0] + } + } + + const Tab = FlowView.allTabs[_.capitalize(active)] + + return ( + <div className="flow-detail" onScroll={this.adjustHead}> + <Nav + flow={flow} + tabs={tabs} + active={active} + onSelectTab={this.selectTab} + /> + <Tab ref="tab" flow={flow}/> + {this.state.prompt && ( + <Prompt {...this.state.prompt}/> + )} + </div> + ) + } +} diff --git a/web/src/js/components/FlowView/Details.jsx b/web/src/js/components/FlowView/Details.jsx new file mode 100644 index 00000000..78e68ecf --- /dev/null +++ b/web/src/js/components/FlowView/Details.jsx @@ -0,0 +1,133 @@ +import React from 'react' +import _ from 'lodash' +import { formatTimeStamp, formatTimeDelta } from '../../utils.js' + +export function TimeStamp({ t, deltaTo, title }) { + return t ? ( + <tr> + <td>{title}:</td> + <td> + {formatTimeStamp(t)} + {deltaTo && ( + <span className="text-muted"> + ({formatTimeDelta(1000 * (t - deltaTo))}) + </span> + )} + </td> + </tr> + ) : ( + <tr></tr> + ) +} + +export function ConnectionInfo({ conn }) { + return ( + <table className="connection-table"> + <tbody> + <tr key="address"> + <td>Address:</td> + <td>{conn.address.address.join(':')}</td> + </tr> + {conn.sni ? ( + <tr key="sni"></tr> + ) : ( + <tr key="sni"> + <td> + <abbr title="TLS Server Name Indication">TLS SNI:</abbr> + </td> + <td>{conn.sni}</td> + </tr> + )} + </tbody> + </table> + ) +} + +export function CertificateInfo({ flow }) { + // @todo We should fetch human-readable certificate representation from the server + return ( + <div> + {flow.client_conn.cert && [ + <h4 key="name">Client Certificate</h4>, + <pre key="value" style={{ maxHeight: 100 }}>{flow.client_conn.cert}</pre> + ]} + + {flow.server_conn.cert && [ + <h4 key="name">Server Certificate</h4>, + <pre key="value" style={{ maxHeight: 100 }}>{flow.server_conn.cert}</pre> + ]} + </div> + ) +} + +export function Timing({ flow }) { + const { server_conn: sc, client_conn: cc, request: req, response: res } = flow + + const timestamps = [ + { + title: "Server conn. initiated", + t: sc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Server conn. TCP handshake", + t: sc.timestamp_tcp_setup, + deltaTo: req.timestamp_start + }, { + title: "Server conn. SSL handshake", + t: sc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "Client conn. established", + t: cc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Client conn. SSL handshake", + t: cc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "First request byte", + t: req.timestamp_start, + }, { + title: "Request complete", + t: req.timestamp_end, + deltaTo: req.timestamp_start + }, res && { + title: "First response byte", + t: res.timestamp_start, + deltaTo: req.timestamp_start + }, res && { + title: "Response complete", + t: res.timestamp_end, + deltaTo: req.timestamp_start + } + ] + + return ( + <div> + <h4>Timing</h4> + <table className="timing-table"> + <tbody> + {timestamps.filter(v => v).sort((a, b) => a.t - b.t).map(item => ( + <TimeStamp key={item.title} {...item}/> + ))} + </tbody> + </table> + </div> + ) +} + +export default function Details({ flow }) { + return ( + <section> + <h4>Client Connection</h4> + <ConnectionInfo conn={flow.client_conn}/> + + <h4>Server Connection</h4> + <ConnectionInfo conn={flow.server_conn}/> + + <CertificateInfo flow={flow}/> + + <Timing flow={flow}/> + </section> + ) +} diff --git a/web/src/js/components/FlowView/Headers.jsx b/web/src/js/components/FlowView/Headers.jsx new file mode 100644 index 00000000..880eeda1 --- /dev/null +++ b/web/src/js/components/FlowView/Headers.jsx @@ -0,0 +1,130 @@ +import React, { Component, PropTypes } from 'react' +import ReactDOM from 'react-dom' +import ValueEditor from '../ValueEditor' +import { Key } from '../../utils.js' + +class HeaderEditor extends Component { + + render() { + return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/> + } + + focus() { + ReactDOM.findDOMNode(this).focus() + } + + onKeyDown(e) { + switch (e.keyCode) { + case Key.BACKSPACE: + var s = window.getSelection().getRangeAt(0) + if (s.startOffset === 0 && s.endOffset === 0) { + this.props.onRemove(e) + } + break + case Key.TAB: + if (!e.shiftKey) { + this.props.onTab(e) + } + break + } + } +} + +export default class Headers extends Component { + + static propTypes = { + onChange: PropTypes.func.isRequired, + message: PropTypes.object.isRequired, + } + + onChange(row, col, val) { + const nextHeaders = _.cloneDeep(this.props.message.headers) + + nextHeaders[row][col] = val + + if (!nextHeaders[row][0] && !nextHeaders[row][1]) { + // do not delete last row + if (nextHeaders.length === 1) { + nextHeaders[0][0] = 'Name' + nextHeaders[0][1] = 'Value' + } else { + nextHeaders.splice(row, 1) + // manually move selection target if this has been the last row. + if (row === nextHeaders.length) { + this._nextSel = `${row - 1}-value` + } + } + } + + this.props.onChange(nextHeaders) + } + + edit() { + this.refs['0-key'].focus() + } + + onTab(row, col, e) { + const headers = this.props.message.headers + + if (row !== headers.length - 1 || col !== 1) { + return + } + + e.preventDefault() + + const nextHeaders = _.cloneDeep(this.props.message.headers) + nextHeaders.push(['Name', 'Value']) + this.props.onChange(nextHeaders) + this._nextSel = `${row + 1}-key` + } + + componentDidUpdate() { + if (this._nextSel && this.refs[this._nextSel]) { + this.refs[this._nextSel].focus() + this._nextSel = undefined + } + } + + onRemove(row, col, e) { + if (col === 1) { + e.preventDefault() + this.refs[`${row}-key`].focus() + } else if (row > 0) { + e.preventDefault() + this.refs[`${row - 1}-value`].focus() + } + } + + render() { + const { message } = this.props + + return ( + <table className="header-table"> + <tbody> + {message.headers.map((header, i) => ( + <tr key={i}> + <td className="header-name"> + <HeaderEditor + ref={`${i}-key`} + content={header[0]} + onDone={val => this.onChange(i, 0, val)} + onRemove={event => this.onRemove(i, 0, event)} + onTab={event => this.onTab(i, 0, event)} + />: + </td> + <td className="header-value"> + <HeaderEditor + ref={`${i}-value`} + content={header[1]} + onDone={val => this.onChange(i, 1, val)} + onRemove={event => this.onRemove(i, 1, event)} + onTab={event => this.onTab(i, 1, event)} + /> + </td> + </tr> + ))} + </tbody> + </table> + ) + } +} diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx new file mode 100644 index 00000000..ba6a5f2b --- /dev/null +++ b/web/src/js/components/FlowView/Messages.jsx @@ -0,0 +1,168 @@ +import React, { Component } from 'react' +import _ from 'lodash' + +import { FlowActions } from '../../actions.js' +import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js' +import { Key, formatTimeStamp } from '../../utils.js' +import ContentView from '../ContentView' +import ValueEditor from '../ValueEditor' +import Headers from './Headers' + +class RequestLine extends Component { + + render() { + const { flow } = this.props + + return ( + <div className="first-line request-line"> + <ValueEditor + ref="method" + content={flow.request.method} + onDone={method => FlowActions.update(flow, { request: { method } })} + inline + /> + + <ValueEditor + ref="url" + content={RequestUtils.pretty_url(flow.request)} + onDone={url => FlowActions.update(flow, { request: Object.assign({ path: '' }, parseUrl(url)) })} + isValid={url => !!parseUrl(url).host} + inline + /> + + <ValueEditor + ref="httpVersion" + content={flow.request.http_version} + onDone={ver => FlowActions.update(flow, { request: { http_version: parseHttpVersion(ver) } })} + isValid={isValidHttpVersion} + inline + /> + </div> + ) + } +} + +class ResponseLine extends Component { + + render() { + const { flow } = this.props + + return ( + <div className="first-line response-line"> + <ValueEditor + ref="httpVersion" + content={flow.response.http_version} + onDone={nextVer => FlowActions.update(flow, { response: { http_version: parseHttpVersion(nextVer) } })} + isValid={isValidHttpVersion} + inline + /> + + <ValueEditor + ref="code" + content={flow.response.status_code + ''} + onDone={code => FlowActions.update(flow, { response: { code: parseInt(code) } })} + isValid={code => /^\d+$/.test(code)} + inline + /> + + <ValueEditor + ref="msg" + content={flow.response.reason} + onDone={msg => FlowActions.update(flow, { response: { msg } })} + inline + /> + </div> + ) + } +} + +export class Request extends Component { + + render() { + const { flow } = this.props + + return ( + <section className="request"> + <RequestLine ref="requestLine" flow={flow}/> + <Headers + ref="headers" + message={flow.request} + onChange={headers => FlowActions.update(flow, { request: { headers } })} + /> + <hr/> + <ContentView flow={flow} message={flow.request}/> + </section> + ) + } + + edit(k) { + switch (k) { + case 'm': + this.refs.requestLine.refs.method.focus() + break + case 'u': + this.refs.requestLine.refs.url.focus() + break + case 'v': + this.refs.requestLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`Unimplemented: ${k}`) + } + } +} + +export class Response extends Component { + + render() { + const { flow } = this.props + + return ( + <section className="response"> + <ResponseLine ref="responseLine" flow={flow}/> + <Headers + ref="headers" + message={flow.response} + onChange={headers => FlowActions.update(flow, { response: { headers } })} + /> + <hr/> + <ContentView flow={flow} message={flow.response}/> + </section> + ) + } + + edit(k) { + switch (k) { + case 'c': + this.refs.responseLine.refs.status_code.focus() + break + case 'm': + this.refs.responseLine.refs.msg.focus() + break + case 'v': + this.refs.responseLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`'Unimplemented: ${k}`) + } + } +} + +export function Error({ flow }) { + return ( + <section> + <div className="alert alert-warning"> + {flow.error.msg} + <div> + <small>{formatTimeStamp(flow.error.timestamp)}</small> + </div> + </div> + </section> + ) +} diff --git a/web/src/js/components/FlowView/Nav.jsx b/web/src/js/components/FlowView/Nav.jsx new file mode 100644 index 00000000..386c3a6c --- /dev/null +++ b/web/src/js/components/FlowView/Nav.jsx @@ -0,0 +1,57 @@ +import React, { PropTypes } from 'react' +import classnames from 'classnames' +import { FlowActions } from '../../actions.js' + +NavAction.propTypes = { + icon: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +} + +function NavAction({ icon, title, onClick }) { + return ( + <a title={title} + href="#" + className="nav-action" + onClick={event => { + event.preventDefault() + onClick(event) + }}> + <i className={`fa fa-fw ${icon}`}></i> + </a> + ) +} + +Nav.propTypes = { + flow: PropTypes.object.isRequired, + active: PropTypes.string.isRequired, + tabs: PropTypes.array.isRequired, + onSelectTab: PropTypes.func.isRequired, +} + +export default function Nav({ flow, active, tabs, onSelectTab }) { + return ( + <nav className="nav-tabs nav-tabs-sm"> + {tabs.map(tab => ( + <a key={tab} + href="#" + className={classnames({ active: active === tab })} + onClick={event => { + event.preventDefault() + onSelectTab(tab) + }}> + {_.capitalize(tab)} + </a> + ))} + <NavAction title="[d]elete flow" icon="fa-trash" onClick={() => FlowActions.delete(flow)} /> + <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={() => FlowActions.duplicate(flow)} /> + <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={() => FlowActions.replay(flow)} /> + {flow.intercepted && ( + <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={() => FlowActions.accept(flow)} /> + )} + {flow.modified && ( + <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={() => FlowActions.revert(flow)} /> + )} + </nav> + ) +} diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 903522f4..1f6de2d7 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -1,6 +1,5 @@ import React from 'react' import { formatSize } from '../utils.js' -import { SettingsState } from './common.js' Footer.propTypes = { settings: React.PropTypes.object.isRequired, diff --git a/web/src/js/components/Header.js b/web/src/js/components/Header.jsx index 93ca5154..93ca5154 100644 --- a/web/src/js/components/Header.js +++ b/web/src/js/components/Header.jsx diff --git a/web/src/js/components/Header/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index 4a43f40f..96f42652 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -1,10 +1,10 @@ import React, { PropTypes } from 'react' -import { Button } from '../common.js' -import {FlowActions} from "../../actions.js"; -import {MessageUtils} from "../../flow/utils.js"; +import Button from '../common/Button' +import { FlowActions } from '../../actions.js' +import { MessageUtils } from '../../flow/utils.js' import { connect } from 'react-redux' -FlowMenu.title = "Flow" +FlowMenu.title = 'Flow' FlowMenu.propTypes = { flow: PropTypes.object.isRequired, diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index 6bbf15d5..44f309fd 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -1,8 +1,9 @@ import React, { PropTypes } from 'react' -import { ToggleInputButton, ToggleButton } from '../common.js' +import ToggleButton from '../common/ToggleButton' +import ToggleInputButton from '../common/ToggleInputButton' import { SettingsActions } from '../../actions.js' -OptionMenu.title = "Options" +OptionMenu.title = 'Options' OptionMenu.propTypes = { settings: PropTypes.object.isRequired, diff --git a/web/src/js/components/Header/ViewMenu.jsx b/web/src/js/components/Header/ViewMenu.jsx index a58b0a16..8d662c28 100644 --- a/web/src/js/components/Header/ViewMenu.jsx +++ b/web/src/js/components/Header/ViewMenu.jsx @@ -1,6 +1,6 @@ import React, { PropTypes } from 'react' import { connect } from 'react-redux' -import { ToggleButton } from '../common.js' +import ToggleButton from '../common/ToggleButton' import { toggleEventLogVisibility } from '../../ducks/eventLog' ViewMenu.title = 'View' diff --git a/web/src/js/components/MainView.jsx b/web/src/js/components/MainView.jsx index 8c6ed6d0..7064d3bf 100644 --- a/web/src/js/components/MainView.jsx +++ b/web/src/js/components/MainView.jsx @@ -3,9 +3,9 @@ import { connect } from 'react-redux' import { FlowActions } from '../actions.js' import { Query } from '../actions.js' import { Key } from '../utils.js' -import { Splitter } from './common.js' +import Splitter from './common/Splitter' import FlowTable from './FlowTable' -import FlowView from './flowview/index.js' +import FlowView from './FlowView' import { selectFlow, setFilter, setHighlight } from '../ducks/flows' class MainView extends Component { diff --git a/web/src/js/components/Prompt.jsx b/web/src/js/components/Prompt.jsx new file mode 100755 index 00000000..6b19b3b3 --- /dev/null +++ b/web/src/js/components/Prompt.jsx @@ -0,0 +1,71 @@ +import React, { PropTypes } from 'react' +import ReactDOM from 'react-dom' +import _ from 'lodash' + +import {Key} from '../utils.js' + +Prompt.contextTypes = { + returnFocus: PropTypes.func +} + +Prompt.propTypes = { + options: PropTypes.array.isRequired, + done: PropTypes.func.isRequired, + prompt: PropTypes.string, +} + +export default function Prompt({ prompt, done, options }, context) { + const opts = [] + + function keyTaken(k) { + return _.map(opts, 'key').includes(k) + } + + for (let i = 0; i < options.length; i++) { + let opt = options[i] + if (_.isString(opt)) { + let str = opt + while (str.length > 0 && keyTaken(str[0])) { + str = str.substr(1) + } + opt = { text: opt, key: str[0] } + } + if (!opt.text || !opt.key || keyTaken(opt.key)) { + throw 'invalid options' + } + opts.push(opt) + } + + return ( + <div tabIndex="0" onKeyDown={onKeyDown} onClick={onClick} className="prompt-dialog"> + <div className="prompt-content"> + {prompt || <strong>Select: </strong> } + {opts.map(opt => { + const idx = opt.text.indexOf(opt.key) + function onClick(event) { + done(opt.key) + event.stopPropagation() + } + return ( + <span key={opt.key} className="option" onClick={onClick}> + {idx !== -1 ? opt.text.substring(0, idx) : opt.text + '('} + {prefix}<strong className="text-primary">{opt.key}</strong> + {idx !== -1 ? opt.text.substring(idx + 1) : ')'} + </span> + ) + })} + </div> + </div> + ) + + function onKeyDown(event) { + event.stopPropagation() + event.preventDefault() + const key = opts.find(opt => Key[opt.key.toUpperCase()] === event.keyCode) + if (!key && event.keyCode !== Key.ESC && event.keyCode !== Key.ENTER) { + return + } + done(k || false) + context.returnFocus() + } +} diff --git a/web/src/js/components/ProxyApp.jsx b/web/src/js/components/ProxyApp.jsx index 2aedba7c..b5d59b76 100644 --- a/web/src/js/components/ProxyApp.jsx +++ b/web/src/js/components/ProxyApp.jsx @@ -3,7 +3,6 @@ import ReactDOM from 'react-dom' import _ from 'lodash' import { connect } from 'react-redux' -import { Splitter } from './common.js' import { connect as wsConnect } from '../ducks/websocket' import Header from './Header' import EventLog from './EventLog' @@ -15,7 +14,6 @@ class ProxyAppMain extends Component { static childContextTypes = { returnFocus: PropTypes.func.isRequired, - location: PropTypes.object.isRequired, } static contextTypes = { @@ -72,7 +70,7 @@ class ProxyAppMain extends Component { */ componentDidMount() { this.focus() - this.settingsStore.addListener("recalculate", this.onSettingsChange) + this.settingsStore.addListener('recalculate', this.onSettingsChange) } /** @@ -81,7 +79,7 @@ class ProxyAppMain extends Component { * @todo stop listening to window's key events */ componentWillUnmount() { - this.settingsStore.removeListener("recalculate", this.onSettingsChange) + this.settingsStore.removeListener('recalculate', this.onSettingsChange) } /** @@ -95,10 +93,7 @@ class ProxyAppMain extends Component { * @todo use props */ getChildContext() { - return { - returnFocus: this.focus, - location: this.props.location - } + return { returnFocus: this.focus } } /** @@ -112,19 +107,20 @@ class ProxyAppMain extends Component { /** * @todo move to actions + * @todo bind on window */ onKeyDown(e) { let name = null switch (e.keyCode) { case Key.I: - name = "intercept" + name = 'intercept' break case Key.L: - name = "search" + name = 'search' break case Key.H: - name = "highlight" + name = 'highlight' break default: let main = this.refs.view @@ -139,7 +135,7 @@ class ProxyAppMain extends Component { if (name) { const headerComponent = this.refs.header - headerComponent.setState({ active: Header.entries.MainMenu }, () => { + headerComponent.setState({ active: Header.entries[0] }, () => { headerComponent.refs.active.refs[name].select() }) } @@ -156,12 +152,11 @@ class ProxyAppMain extends Component { <Header ref="header" settings={settings} updateLocation={this.updateLocation} query={query} /> {React.cloneElement( children, - { ref: "view", location, query, updateLocation: this.updateLocation } + { ref: 'view', location, query, updateLocation: this.updateLocation } )} - {showEventLog && [ - <Splitter key="splitter" axis="y"/>, + {showEventLog && ( <EventLog key="eventlog"/> - ]} + )} <Footer settings={settings}/> </div> ) diff --git a/web/src/js/components/ValueEditor.jsx b/web/src/js/components/ValueEditor.jsx new file mode 100755 index 00000000..0316924f --- /dev/null +++ b/web/src/js/components/ValueEditor.jsx @@ -0,0 +1,36 @@ +import React, { Component, PropTypes } from 'react' +import ReactDOM from 'react-dom' +import ValidateEditor from './ValueEditor/ValidateEditor' + +export default class ValueEditor extends Component { + + static contextTypes = { + returnFocus: PropTypes.func, + } + + static propTypes = { + content: PropTypes.string.isRequired, + onDone: PropTypes.func.isRequired, + inline: PropTypes.bool, + } + + constructor(props) { + super(props) + this.focus = this.focus.bind(this) + } + + render() { + var tag = this.props.inline ? "span" : 'div' + return ( + <ValidateEditor + {...this.props} + onStop={() => this.context.returnFocus()} + tag={tag} + /> + ) + } + + focus() { + ReactDOM.findDOMNode(this).focus(); + } +} diff --git a/web/src/js/components/ValueEditor/EditorBase.jsx b/web/src/js/components/ValueEditor/EditorBase.jsx new file mode 100755 index 00000000..e737d2af --- /dev/null +++ b/web/src/js/components/ValueEditor/EditorBase.jsx @@ -0,0 +1,166 @@ +import React, { Component, PropTypes } from 'react' +import ReactDOM from 'react-dom' +import {Key} from '../../utils.js' + +export default class EditorBase extends Component { + + static propTypes = { + content: PropTypes.string.isRequired, + onDone: PropTypes.func.isRequired, + contentToHtml: PropTypes.func, + nodeToContent: PropTypes.func, + onStop: PropTypes.func, + submitOnEnter: PropTypes.bool, + className: PropTypes.string, + tag: PropTypes.string, + } + + static defaultProps = { + contentToHtml: content => _.escape(content), + nodeToContent: node => node.textContent, + submitOnEnter: true, + className: '', + tag: 'div', + onStop: _.noop, + onMouseDown: _.noop, + onBlur: _.noop, + onInput: _.noop, + } + + constructor(props) { + super(props) + this.state = {editable: false} + + this.onPaste = this.onPaste.bind(this) + this.onMouseDown = this.onMouseDown.bind(this) + this.onMouseUp = this.onMouseUp.bind(this) + this.onFocus = this.onFocus.bind(this) + this.onClick = this.onClick.bind(this) + this.stop = this.stop.bind(this) + this.onBlur = this.onBlur.bind(this) + this.reset = this.reset.bind(this) + this.onKeyDown = this.onKeyDown.bind(this) + this.onInput = this.onInput.bind(this) + } + + stop() { + // a stop would cause a blur as a side-effect. + // but a blur event must trigger a stop as well. + // to fix this, make stop = blur and do the actual stop in the onBlur handler. + ReactDOM.findDOMNode(this).blur() + this.props.onStop() + } + + render() { + return ( + <this.props.tag + {...this.props} + tabIndex="0" + className={`inline-input ${this.props.className}`} + contentEditable={this.state.editable || undefined} + onFocus={this.onFocus} + onMouseDown={this.onMouseDown} + onClick={this.onClick} + onBlur={this.onBlur} + onKeyDown={this.onKeyDown} + onInput={this.onInput} + onPaste={this.onPaste} + dangerouslySetInnerHTML={{ __html: this.props.contentToHtml(this.props.content) }} + /> + ) + } + + onPaste(e) { + e.preventDefault() + var content = e.clipboardData.getData('text/plain') + document.execCommand('insertHTML', false, content) + } + + onMouseDown(e) { + this._mouseDown = true + window.addEventListener('mouseup', this.onMouseUp) + this.props.onMouseDown(e) + } + + onMouseUp() { + if (this._mouseDown) { + this._mouseDown = false + window.removeEventListener('mouseup', this.onMouseUp) + } + } + + onClick(e) { + this.onMouseUp() + this.onFocus(e) + } + + onFocus(e) { + if (this._mouseDown || this._ignore_events || this.state.editable) { + return + } + + // contenteditable in FireFox is more or less broken. + // - we need to blur() and then focus(), otherwise the caret is not shown. + // - blur() + focus() == we need to save the caret position before + // Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint + const sel = window.getSelection() + let range + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0) + } else if (document.caretPositionFromPoint && e.clientX && e.clientY) { + const pos = document.caretPositionFromPoint(e.clientX, e.clientY) + range = document.createRange() + range.setStart(pos.offsetNode, pos.offset) + } else if (document.caretRangeFromPoint && e.clientX && e.clientY) { + range = document.caretRangeFromPoint(e.clientX, e.clientY) + } else { + range = document.createRange() + range.selectNodeContents(ReactDOM.findDOMNode(this)) + } + + this._ignore_events = true + this.setState({ editable: true }, () => { + const node = ReactDOM.findDOMNode(this) + node.blur() + node.focus() + this._ignore_events = false + }) + } + + onBlur(e) { + if (this._ignore_events) { + return + } + window.getSelection().removeAllRanges() //make sure that selection is cleared on blur + this.setState({ editable: false }) + this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this))) + this.props.onBlur(e) + } + + reset() { + ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content) + } + + onKeyDown(e) { + e.stopPropagation() + switch (e.keyCode) { + case Key.ESC: + e.preventDefault() + this.reset() + this.stop() + break + case Key.ENTER: + if (this.props.submitOnEnter && !e.shiftKey) { + e.preventDefault() + this.stop() + } + break + default: + break + } + } + + onInput() { + this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this))) + } +} diff --git a/web/src/js/components/ValueEditor/ValidateEditor.jsx b/web/src/js/components/ValueEditor/ValidateEditor.jsx new file mode 100755 index 00000000..2f362986 --- /dev/null +++ b/web/src/js/components/ValueEditor/ValidateEditor.jsx @@ -0,0 +1,58 @@ +import React, { Component, PropTypes } from 'react' +import ReactDOM from 'react-dom' +import EditorBase from './EditorBase' + +export default class ValidateEditor extends Component { + + static propTypes = { + content: PropTypes.string.isRequired, + onDone: PropTypes.func.isRequired, + onInput: PropTypes.func, + isValid: PropTypes.func, + className: PropTypes.string, + } + + constructor(props) { + super(props) + this.state = { currentContent: props.content } + this.onInput = this.onInput.bind(this) + this.onDone = this.onDone.bind(this) + } + + componentWillReceiveProps(nextProps) { + this.setState({ currentContent: nextProps.content }) + } + + onInput(currentContent) { + this.setState({ currentContent }) + this.props.onInput && this.props.onInput(currentContent) + } + + onDone(content) { + if (this.props.isValid && !this.props.isValid(content)) { + this.refs.editor.reset() + content = this.props.content + } + this.props.onDone(content) + } + + render() { + let className = this.props.className || '' + if (this.props.isValid) { + if (this.props.isValid(this.state.currentContent)) { + className += ' has-success' + } else { + className += ' has-warning' + } + } + return ( + <EditorBase + {...this.props} + ref="editor" + className={className} + onDone={this.onDone} + onInput={this.onInput} + /> + ) + } +} diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js deleted file mode 100644 index 1497e15d..00000000 --- a/web/src/js/components/common.js +++ /dev/null @@ -1,173 +0,0 @@ -import React from "react" -import ReactDOM from "react-dom" -import {Key} from "../utils.js"; -import _ from "lodash" - -export var Splitter = React.createClass({ - getDefaultProps: function () { - return { - axis: "x" - }; - }, - getInitialState: function () { - return { - applied: false, - startX: false, - startY: false - }; - }, - onMouseDown: function (e) { - this.setState({ - startX: e.pageX, - startY: e.pageY - }); - window.addEventListener("mousemove", this.onMouseMove); - window.addEventListener("mouseup", this.onMouseUp); - // Occasionally, only a dragEnd event is triggered, but no mouseUp. - window.addEventListener("dragend", this.onDragEnd); - }, - onDragEnd: function () { - ReactDOM.findDOMNode(this).style.transform = ""; - window.removeEventListener("dragend", this.onDragEnd); - window.removeEventListener("mouseup", this.onMouseUp); - window.removeEventListener("mousemove", this.onMouseMove); - }, - onMouseUp: function (e) { - this.onDragEnd(); - - var node = ReactDOM.findDOMNode(this); - var prev = node.previousElementSibling; - var next = node.nextElementSibling; - - var dX = e.pageX - this.state.startX; - var dY = e.pageY - this.state.startY; - var flexBasis; - if (this.props.axis === "x") { - flexBasis = prev.offsetWidth + dX; - } else { - flexBasis = prev.offsetHeight + dY; - } - - prev.style.flex = "0 0 " + Math.max(0, flexBasis) + "px"; - next.style.flex = "1 1 auto"; - - this.setState({ - applied: true - }); - this.onResize(); - }, - onMouseMove: function (e) { - var dX = 0, dY = 0; - if (this.props.axis === "x") { - dX = e.pageX - this.state.startX; - } else { - dY = e.pageY - this.state.startY; - } - ReactDOM.findDOMNode(this).style.transform = "translate(" + dX + "px," + dY + "px)"; - }, - onResize: function () { - // Trigger a global resize event. This notifies components that employ virtual scrolling - // that their viewport may have changed. - window.setTimeout(function () { - window.dispatchEvent(new CustomEvent("resize")); - }, 1); - }, - reset: function (willUnmount) { - if (!this.state.applied) { - return; - } - var node = ReactDOM.findDOMNode(this); - var prev = node.previousElementSibling; - var next = node.nextElementSibling; - - prev.style.flex = ""; - next.style.flex = ""; - - if (!willUnmount) { - this.setState({ - applied: false - }); - } - this.onResize(); - }, - componentWillUnmount: function () { - this.reset(true); - }, - render: function () { - var className = "splitter"; - if (this.props.axis === "x") { - className += " splitter-x"; - } else { - className += " splitter-y"; - } - return ( - <div className={className}> - <div onMouseDown={this.onMouseDown} draggable="true"></div> - </div> - ); - } -}); - -export const ToggleButton = ({checked, onToggle, text}) => - <div className={"btn btn-toggle " + (checked ? "btn-primary" : "btn-default")} onClick={onToggle}> - <i className={"fa fa-fw " + (checked ? "fa-check-square-o" : "fa-square-o")}/> - - {text} - </div>; - -ToggleButton.propTypes = { - checked: React.PropTypes.bool.isRequired, - onToggle: React.PropTypes.func.isRequired, - text: React.PropTypes.string.isRequired -}; - -export const Button = ({onClick, text, icon}) => - <div className={"btn btn-default"} onClick={onClick}> - <i className={"fa fa-fw " + icon}/> - - {text} - </div>; - -Button.propTypes = { - onClick: React.PropTypes.func.isRequired, - text: React.PropTypes.string.isRequired -}; - -export class ToggleInputButton extends React.Component { - constructor(props) { - super(props); - this.state = {txt: props.txt}; - } - - render() { - return ( - <div className="input-group toggle-input-btn"> - <span - className="input-group-btn" - onClick={() => this.props.onToggleChanged(this.state.txt)}> - <div className={"btn " + (this.props.checked ? "btn-primary" : "btn-default")}> - <span className={"fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o")}/> - {this.props.name} - </div> - </span> - <input - className="form-control" - placeholder={this.props.placeholder} - disabled={this.props.checked} - value={this.state.txt} - type={this.props.inputType} - onChange={e => this.setState({txt: e.target.value})} - onKeyDown={e => {if (e.keyCode === Key.ENTER) this.props.onToggleChanged(this.state.txt); e.stopPropagation()}}/> - </div> - ); - } -} - -ToggleInputButton.propTypes = { - name: React.PropTypes.string.isRequired, - txt: React.PropTypes.string.isRequired, - onToggleChanged: React.PropTypes.func.isRequired -}; - - - diff --git a/web/src/js/components/common/Button.jsx b/web/src/js/components/common/Button.jsx new file mode 100644 index 00000000..cc2fe9dd --- /dev/null +++ b/web/src/js/components/common/Button.jsx @@ -0,0 +1,16 @@ +import React, { PropTypes } from 'react' + +Button.propTypes = { + onClick: PropTypes.func.isRequired, + text: PropTypes.string.isRequired +} + +export default function Button({ onClick, text, icon }) { + return ( + <div className={"btn btn-default"} onClick={onClick}> + <i className={"fa fa-fw " + icon}/> + + {text} + </div> + ) +} diff --git a/web/src/js/components/common/Splitter.jsx b/web/src/js/components/common/Splitter.jsx new file mode 100644 index 00000000..9d22b6fd --- /dev/null +++ b/web/src/js/components/common/Splitter.jsx @@ -0,0 +1,99 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import classnames from 'classnames' + +export default class Splitter extends Component { + + static defaultProps = { axis: 'x' } + + constructor(props, context) { + super(props, context) + + this.state = { applied: false, startX: false, startY: false } + + this.onMouseMove = this.onMouseMove.bind(this) + this.onMouseUp = this.onMouseUp.bind(this) + this.onDragEnd = this.onDragEnd.bind(this) + } + + onMouseDown(e) { + this.setState({ startX: e.pageX, startY: e.pageY }) + + window.addEventListener('mousemove', this.onMouseMove) + window.addEventListener('mouseup', this.onMouseUp) + // Occasionally, only a dragEnd event is triggered, but no mouseUp. + window.addEventListener('dragend', this.onDragEnd) + } + + onDragEnd() { + ReactDOM.findDOMNode(this).style.transform = '' + + window.removeEventListener('dragend', this.onDragEnd) + window.removeEventListener('mouseup', this.onMouseUp) + window.removeEventListener('mousemove', this.onMouseMove) + } + + onMouseUp(e) { + this.onDragEnd() + + const node = ReactDOM.findDOMNode(this) + const prev = node.previousElementSibling + + let flexBasis = prev.offsetHeight + e.pageY - this.state.startY + + if (this.props.axis === 'x') { + flexBasis = prev.offsetWidth + e.pageX - this.state.startX + } + + prev.style.flex = `0 0 ${Math.max(0, flexBasis)}px` + node.nextElementSibling.style.flex = '1 1 auto' + + this.setState({ applied: true }) + this.onResize() + } + + onMouseMove(e) { + let dX = 0 + let dY = 0 + if (this.props.axis === 'x') { + dX = e.pageX - this.state.startX + } else { + dY = e.pageY - this.state.startY + } + ReactDOM.findDOMNode(this).style.transform = `translate(${dX}px, ${dY}px)` + } + + onResize() { + // Trigger a global resize event. This notifies components that employ virtual scrolling + // that their viewport may have changed. + window.setTimeout(() => window.dispatchEvent(new CustomEvent('resize')), 1) + } + + reset(willUnmount) { + if (!this.state.applied) { + return + } + + const node = ReactDOM.findDOMNode(this) + + node.previousElementSibling.style.flex = '' + node.nextElementSibling.style.flex = '' + + if (!willUnmount) { + this.setState({ applied: false }) + } + this.onResize() + } + + componentWillUnmount() { + this.reset(true) + } + + render() { + return ( + <div className={classnames('splitter', this.props.axis === 'x' ? 'splitter-x' : 'splitter-y')}> + <div onMouseDown={this.onMouseDown} draggable="true"></div> + </div> + ) + } +} diff --git a/web/src/js/components/common/ToggleButton.jsx b/web/src/js/components/common/ToggleButton.jsx new file mode 100644 index 00000000..6027728b --- /dev/null +++ b/web/src/js/components/common/ToggleButton.jsx @@ -0,0 +1,17 @@ +import React, { PropTypes } from 'react' + +ToggleButton.propTypes = { + checked: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired, + text: PropTypes.string.isRequired +} + +export default function ToggleButton({ checked, onToggle, text }) { + return ( + <div className={"btn btn-toggle " + (checked ? "btn-primary" : "btn-default")} onClick={onToggle}> + <i className={"fa fa-fw " + (checked ? "fa-check-square-o" : "fa-square-o")}/> + + {text} + </div> + ) +} diff --git a/web/src/js/components/common/ToggleInputButton.jsx b/web/src/js/components/common/ToggleInputButton.jsx new file mode 100644 index 00000000..25d620ae --- /dev/null +++ b/web/src/js/components/common/ToggleInputButton.jsx @@ -0,0 +1,52 @@ +import React, { Component, PropTypes } from 'react' +import classnames from 'classnames' +import { Key } from '../../utils' + +export default class ToggleInputButton extends Component { + + static propTypes = { + name: PropTypes.string.isRequired, + txt: PropTypes.string.isRequired, + onToggleChanged: PropTypes.func.isRequired + } + + constructor(props) { + super(props) + this.state = { txt: props.txt } + } + + onChange(e) { + this.setState({ txt: e.target.value }) + } + + onKeyDown(e) { + e.stopPropagation() + if (e.keyCode === Key.ENTER) { + this.props.onToggleChanged(this.state.txt) + } + } + + render() { + return ( + <div className="input-group toggle-input-btn"> + <span className="input-group-btn" + onClick={() => this.props.onToggleChanged(this.state.txt)}> + <div className={classnames('btn', this.props.checked ? 'btn-primary' : 'btn-default')}> + <span className={classnames('fa', this.props.checked ? 'fa-check-square-o' : 'fa-square-o')}/> + + {this.props.name} + </div> + </span> + <input + className="form-control" + placeholder={this.props.placeholder} + disabled={this.props.checked} + value={this.state.txt} + type={this.props.inputType} + onChange={e => this.onChange(e)} + onKeyDown={e => this.onKeyDown(e)} + /> + </div> + ) + } +} diff --git a/web/src/js/components/editor.js b/web/src/js/components/editor.js deleted file mode 100644 index eed2f7c6..00000000 --- a/web/src/js/components/editor.js +++ /dev/null @@ -1,238 +0,0 @@ -import React from "react"; -import ReactDOM from 'react-dom'; -import {Key} from "../utils.js"; - -var contentToHtml = function (content) { - return _.escape(content); -}; -var nodeToContent = function (node) { - return node.textContent; -}; - -/* - Basic Editor Functionality - */ -var EditorBase = React.createClass({ - propTypes: { - content: React.PropTypes.string.isRequired, - onDone: React.PropTypes.func.isRequired, - contentToHtml: React.PropTypes.func, - nodeToContent: React.PropTypes.func, // content === nodeToContent( Node<innerHTML=contentToHtml(content)> ) - onStop: React.PropTypes.func, - submitOnEnter: React.PropTypes.bool, - className: React.PropTypes.string, - tag: React.PropTypes.string - }, - getDefaultProps: function () { - return { - contentToHtml: contentToHtml, - nodeToContent: nodeToContent, - submitOnEnter: true, - className: "", - tag: "div" - }; - }, - getInitialState: function () { - return { - editable: false - }; - }, - render: function () { - var className = "inline-input " + this.props.className; - var html = {__html: this.props.contentToHtml(this.props.content)}; - var Tag = this.props.tag; - return <Tag - {...this.props} - tabIndex="0" - className={className} - contentEditable={this.state.editable || undefined } // workaround: use undef instead of false to remove attr - onFocus={this.onFocus} - onMouseDown={this.onMouseDown} - onClick={this.onClick} - onBlur={this._stop} - onKeyDown={this.onKeyDown} - onInput={this.onInput} - onPaste={this.onPaste} - dangerouslySetInnerHTML={html} - />; - }, - onPaste: function (e) { - e.preventDefault(); - var content = e.clipboardData.getData("text/plain"); - document.execCommand("insertHTML", false, content); - }, - onMouseDown: function (e) { - this._mouseDown = true; - window.addEventListener("mouseup", this.onMouseUp); - this.props.onMouseDown && this.props.onMouseDown(e); - }, - onMouseUp: function () { - if (this._mouseDown) { - this._mouseDown = false; - window.removeEventListener("mouseup", this.onMouseUp) - } - }, - onClick: function (e) { - this.onMouseUp(); - this.onFocus(e); - }, - onFocus: function (e) { - console.log("onFocus", this._mouseDown, this._ignore_events, this.state.editable); - if (this._mouseDown || this._ignore_events || this.state.editable) { - return; - } - - //contenteditable in FireFox is more or less broken. - // - we need to blur() and then focus(), otherwise the caret is not shown. - // - blur() + focus() == we need to save the caret position before - // Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint - var sel = window.getSelection(); - var range; - if (sel.rangeCount > 0) { - range = sel.getRangeAt(0); - } else if (document.caretPositionFromPoint && e.clientX && e.clientY) { - var pos = document.caretPositionFromPoint(e.clientX, e.clientY); - range = document.createRange(); - range.setStart(pos.offsetNode, pos.offset); - } else if (document.caretRangeFromPoint && e.clientX && e.clientY) { - range = document.caretRangeFromPoint(e.clientX, e.clientY); - } else { - range = document.createRange(); - range.selectNodeContents(ReactDOM.findDOMNode(this)); - } - - this._ignore_events = true; - this.setState({editable: true}, function () { - var node = ReactDOM.findDOMNode(this); - node.blur(); - node.focus(); - this._ignore_events = false; - //sel.removeAllRanges(); - //sel.addRange(range); - - - }); - }, - stop: function () { - // a stop would cause a blur as a side-effect. - // but a blur event must trigger a stop as well. - // to fix this, make stop = blur and do the actual stop in the onBlur handler. - ReactDOM.findDOMNode(this).blur(); - this.props.onStop && this.props.onStop(); - }, - _stop: function (e) { - if (this._ignore_events) { - return; - } - console.log("_stop", _.extend({}, e)); - window.getSelection().removeAllRanges(); //make sure that selection is cleared on blur - var node = ReactDOM.findDOMNode(this); - var content = this.props.nodeToContent(node); - this.setState({editable: false}); - this.props.onDone(content); - this.props.onBlur && this.props.onBlur(e); - }, - reset: function () { - ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content); - }, - onKeyDown: function (e) { - e.stopPropagation(); - switch (e.keyCode) { - case Key.ESC: - e.preventDefault(); - this.reset(); - this.stop(); - break; - case Key.ENTER: - if (this.props.submitOnEnter && !e.shiftKey) { - e.preventDefault(); - this.stop(); - } - break; - default: - break; - } - }, - onInput: function () { - var node = ReactDOM.findDOMNode(this); - var content = this.props.nodeToContent(node); - this.props.onInput && this.props.onInput(content); - } -}); - -/* - Add Validation to EditorBase - */ -var ValidateEditor = React.createClass({ - propTypes: { - content: React.PropTypes.string.isRequired, - onDone: React.PropTypes.func.isRequired, - onInput: React.PropTypes.func, - isValid: React.PropTypes.func, - className: React.PropTypes.string, - }, - getInitialState: function () { - return { - currentContent: this.props.content - }; - }, - componentWillReceiveProps: function () { - this.setState({currentContent: this.props.content}); - }, - onInput: function (content) { - this.setState({currentContent: content}); - this.props.onInput && this.props.onInput(content); - }, - render: function () { - var className = this.props.className || ""; - if (this.props.isValid) { - if (this.props.isValid(this.state.currentContent)) { - className += " has-success"; - } else { - className += " has-warning" - } - } - return <EditorBase - {...this.props} - ref="editor" - className={className} - onDone={this.onDone} - onInput={this.onInput} - />; - }, - onDone: function (content) { - if (this.props.isValid && !this.props.isValid(content)) { - this.refs.editor.reset(); - content = this.props.content; - } - this.props.onDone(content); - } -}); - -/* - Text Editor with mitmweb-specific convenience features - */ -export var ValueEditor = React.createClass({ - contextTypes: { - returnFocus: React.PropTypes.func - }, - propTypes: { - content: React.PropTypes.string.isRequired, - onDone: React.PropTypes.func.isRequired, - inline: React.PropTypes.bool, - }, - render: function () { - var tag = this.props.inline ? "span" : "div"; - return <ValidateEditor - {...this.props} - onStop={this.onStop} - tag={tag} - />; - }, - focus: function () { - ReactDOM.findDOMNode(this).focus(); - }, - onStop: function () { - this.context.returnFocus(); - } -});
\ No newline at end of file diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js deleted file mode 100644 index cbac9a75..00000000 --- a/web/src/js/components/flowview/contentview.js +++ /dev/null @@ -1,267 +0,0 @@ -import React from "react"; -import _ from "lodash"; - -import {MessageUtils} from "../../flow/utils.js"; -import {formatSize} from "../../utils.js"; - -var ViewImage = React.createClass({ - propTypes: { - flow: React.PropTypes.object.isRequired, - message: React.PropTypes.object.isRequired, - }, - statics: { - regex: /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i, - matches: function (message) { - return ViewImage.regex.test(MessageUtils.getContentType(message)); - } - }, - render: function () { - var url = MessageUtils.getContentURL(this.props.flow, this.props.message); - return <div className="flowview-image"> - <img src={url} alt="preview" className="img-thumbnail"/> - </div>; - } -}); - -var ContentLoader = React.createClass({ - propTypes: { - flow: React.PropTypes.object.isRequired, - message: React.PropTypes.object.isRequired, - }, - getInitialState: function () { - return { - content: undefined, - request: undefined - } - }, - requestContent: function (nextProps) { - if (this.state.request) { - this.state.request.abort(); - } - var request = MessageUtils.getContent(nextProps.flow, nextProps.message); - this.setState({ - content: undefined, - request: request - }); - request.done(function (data) { - this.setState({content: data}); - }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { - if (textStatus === "abort") { - return; - } - this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); - }.bind(this)).always(function () { - this.setState({request: undefined}); - }.bind(this)); - - }, - componentWillMount: function () { - this.requestContent(this.props); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.message !== this.props.message) { - this.requestContent(nextProps); - } - }, - componentWillUnmount: function () { - if (this.state.request) { - this.state.request.abort(); - } - }, - render: function () { - if (!this.state.content) { - return <div className="text-center"> - <i className="fa fa-spinner fa-spin"></i> - </div>; - } - return React.cloneElement(this.props.children, { - content: this.state.content - }) - } -}); - -var ViewRaw = React.createClass({ - propTypes: { - content: React.PropTypes.string.isRequired, - }, - statics: { - textView: true, - matches: function (message) { - return true; - } - }, - render: function () { - return <pre>{this.props.content}</pre>; - } -}); - -var ViewJSON = React.createClass({ - propTypes: { - content: React.PropTypes.string.isRequired, - }, - statics: { - textView: true, - regex: /^application\/json$/i, - matches: function (message) { - return ViewJSON.regex.test(MessageUtils.getContentType(message)); - } - }, - render: function () { - var json = this.props.content; - try { - json = JSON.stringify(JSON.parse(json), null, 2); - } catch (e) { - // @noop - } - return <pre>{json}</pre>; - } -}); - -var ViewAuto = React.createClass({ - propTypes: { - message: React.PropTypes.object.isRequired, - flow: React.PropTypes.object.isRequired, - }, - statics: { - matches: function () { - return false; // don't match itself - }, - findView: function (message) { - for (var i = 0; i < all.length; i++) { - if (all[i].matches(message)) { - return all[i]; - } - } - return all[all.length - 1]; - } - }, - render: function () { - var { message, flow } = this.props - var View = ViewAuto.findView(this.props.message); - if (View.textView) { - return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader> - } else { - return <View message={message} flow={flow} /> - } - } -}); - -var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; - -var ContentEmpty = React.createClass({ - render: function () { - var message_name = this.props.flow.request === this.props.message ? "request" : "response"; - return <div className="alert alert-info">No {message_name} content.</div>; - } -}); - -var ContentMissing = React.createClass({ - render: function () { - var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; - return <div className="alert alert-info">{message_name} content missing.</div>; - } -}); - -var TooLarge = React.createClass({ - statics: { - isTooLarge: function (message) { - var max_mb = ViewImage.matches(message) ? 10 : 0.2; - return message.contentLength > 1024 * 1024 * max_mb; - } - }, - render: function () { - var size = formatSize(this.props.message.contentLength); - return <div className="alert alert-warning"> - <button onClick={this.props.onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button> - {size} content size. - </div>; - } -}); - -var ViewSelector = React.createClass({ - render: function () { - var views = []; - for (var i = 0; i < all.length; i++) { - var view = all[i]; - var className = "btn btn-default"; - if (view === this.props.active) { - className += " active"; - } - var text; - if (view === ViewAuto) { - text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", ""); - } else { - text = view.displayName.toLowerCase().replace("view", ""); - } - views.push( - <button - key={view.displayName} - onClick={this.props.selectView.bind(null, view)} - className={className}> - {text} - </button> - ); - } - - return <div className="view-selector btn-group btn-group-xs">{views}</div>; - } -}); - -var ContentView = React.createClass({ - getInitialState: function () { - return { - displayLarge: false, - View: ViewAuto - }; - }, - propTypes: { - // It may seem a bit weird at the first glance: - // Every view takes the flow and the message as props, e.g. - // <Auto flow={flow} message={flow.request}/> - flow: React.PropTypes.object.isRequired, - message: React.PropTypes.object.isRequired, - }, - selectView: function (view) { - this.setState({ - View: view - }); - }, - displayLarge: function () { - this.setState({displayLarge: true}); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.message !== this.props.message) { - this.setState(this.getInitialState()); - } - }, - render: function () { - var { flow, message } = this.props - var message = this.props.message; - if (message.contentLength === 0) { - return <ContentEmpty {...this.props}/>; - } else if (message.contentLength === null) { - return <ContentMissing {...this.props}/>; - } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) { - return <TooLarge {...this.props} onClick={this.displayLarge}/>; - } - - var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); - - return <div> - {this.state.View.textView ? ( - <ContentLoader flow={flow} message={message}><this.state.View content="" /></ContentLoader> - ) : ( - <this.state.View flow={flow} message={message} /> - )} - <div className="view-options text-center"> - <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> - - <a className="btn btn-default btn-xs" href={downloadUrl}> - <i className="fa fa-download"/> - </a> - </div> - </div>; - } -}); - -export default ContentView; diff --git a/web/src/js/components/flowview/details.js b/web/src/js/components/flowview/details.js deleted file mode 100644 index 45fe1292..00000000 --- a/web/src/js/components/flowview/details.js +++ /dev/null @@ -1,181 +0,0 @@ -import React from "react"; -import _ from "lodash"; - -import {formatTimeStamp, formatTimeDelta} from "../../utils.js"; - -var TimeStamp = React.createClass({ - render: function () { - - if (!this.props.t) { - //should be return null, but that triggers a React bug. - return <tr></tr>; - } - - var ts = formatTimeStamp(this.props.t); - - var delta; - if (this.props.deltaTo) { - delta = formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); - delta = <span className="text-muted">{"(" + delta + ")"}</span>; - } else { - delta = null; - } - - return <tr> - <td>{this.props.title + ":"}</td> - <td>{ts} {delta}</td> - </tr>; - } -}); - -var ConnectionInfo = React.createClass({ - - render: function () { - var conn = this.props.conn; - var address = conn.address.address.join(":"); - - var sni = <tr key="sni"></tr>; //should be null, but that triggers a React bug. - if (conn.sni) { - sni = <tr key="sni"> - <td> - <abbr title="TLS Server Name Indication">TLS SNI:</abbr> - </td> - <td>{conn.sni}</td> - </tr>; - } - return ( - <table className="connection-table"> - <tbody> - <tr key="address"> - <td>Address:</td> - <td>{address}</td> - </tr> - {sni} - </tbody> - </table> - ); - } -}); - -var CertificateInfo = React.createClass({ - render: function () { - //TODO: We should fetch human-readable certificate representation - // from the server - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - - var preStyle = {maxHeight: 100}; - return ( - <div> - {client_conn.cert ? <h4>Client Certificate</h4> : null} - {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null} - - {server_conn.cert ? <h4>Server Certificate</h4> : null} - {server_conn.cert ? <pre style={preStyle}>{server_conn.cert}</pre> : null} - </div> - ); - } -}); - -var Timing = React.createClass({ - render: function () { - var flow = this.props.flow; - var sc = flow.server_conn; - var cc = flow.client_conn; - var req = flow.request; - var resp = flow.response; - - var timestamps = [ - { - title: "Server conn. initiated", - t: sc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Server conn. TCP handshake", - t: sc.timestamp_tcp_setup, - deltaTo: req.timestamp_start - }, { - title: "Server conn. SSL handshake", - t: sc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "Client conn. established", - t: cc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Client conn. SSL handshake", - t: cc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "First request byte", - t: req.timestamp_start, - }, { - title: "Request complete", - t: req.timestamp_end, - deltaTo: req.timestamp_start - } - ]; - - if (flow.response) { - timestamps.push( - { - title: "First response byte", - t: resp.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Response complete", - t: resp.timestamp_end, - deltaTo: req.timestamp_start - } - ); - } - - //Add unique key for each row. - timestamps.forEach(function (e) { - e.key = e.title; - }); - - timestamps = _.sortBy(timestamps, 't'); - - var rows = timestamps.map(function (e) { - return <TimeStamp {...e}/>; - }); - - return ( - <div> - <h4>Timing</h4> - <table className="timing-table"> - <tbody> - {rows} - </tbody> - </table> - </div> - ); - } -}); - -var Details = React.createClass({ - render: function () { - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - return ( - <section> - - <h4>Client Connection</h4> - <ConnectionInfo conn={client_conn}/> - - <h4>Server Connection</h4> - <ConnectionInfo conn={server_conn}/> - - <CertificateInfo flow={flow}/> - - <Timing flow={flow}/> - - </section> - ); - } -}); - -export default Details;
\ No newline at end of file diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js deleted file mode 100644 index 7f5e9768..00000000 --- a/web/src/js/components/flowview/index.js +++ /dev/null @@ -1,114 +0,0 @@ -import React from "react"; - -import Nav from "./nav.js"; -import {Request, Response, Error} from "./messages.js"; -import Details from "./details.js"; -import Prompt from "../prompt.js"; - - -var allTabs = { - request: Request, - response: Response, - error: Error, - details: Details -}; - -var FlowView = React.createClass({ - getInitialState: function () { - return { - prompt: false - }; - }, - getTabs: function (flow) { - var tabs = []; - ["request", "response", "error"].forEach(function (e) { - if (flow[e]) { - tabs.push(e); - } - }); - tabs.push("details"); - return tabs; - }, - nextTab: function (i) { - var tabs = this.getTabs(this.props.flow); - var currentIndex = tabs.indexOf(this.props.tab); - // JS modulo operator doesn't correct negative numbers, make sure that we are positive. - var nextIndex = (currentIndex + i + tabs.length) % tabs.length; - this.selectTab(tabs[nextIndex]); - }, - selectTab: function (panel) { - this.props.updateLocation(`/flows/${this.props.flow.id}/${panel}`); - }, - promptEdit: function () { - var options; - switch (this.props.tab) { - case "request": - options = [ - "method", - "url", - {text: "http version", key: "v"}, - "header" - /*, "content"*/]; - break; - case "response": - options = [ - {text: "http version", key: "v"}, - "code", - "message", - "header" - /*, "content"*/]; - break; - case "details": - return; - default: - throw "Unknown tab for edit: " + this.props.tab; - } - - this.setState({ - prompt: { - done: function (k) { - this.setState({prompt: false}); - if (k) { - this.refs.tab.edit(k); - } - }.bind(this), - options: options - } - }); - }, - render: function () { - var flow = this.props.flow; - var tabs = this.getTabs(flow); - var active = this.props.tab; - - if (tabs.indexOf(active) < 0) { - if (active === "response" && flow.error) { - active = "error"; - } else if (active === "error" && flow.response) { - active = "response"; - } else { - active = tabs[0]; - } - } - - var prompt = null; - if (this.state.prompt) { - prompt = <Prompt {...this.state.prompt}/>; - } - - var Tab = allTabs[active]; - return ( - <div className="flow-detail" onScroll={this.adjustHead}> - <Nav ref="head" - flow={flow} - tabs={tabs} - active={active} - selectTab={this.selectTab}/> - <Tab ref="tab" flow={flow}/> - {prompt} - </div> - ); - } -}); - -export default FlowView; diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js deleted file mode 100644 index 2885b3b1..00000000 --- a/web/src/js/components/flowview/messages.js +++ /dev/null @@ -1,320 +0,0 @@ -import React from "react"; -import ReactDOM from 'react-dom'; -import _ from "lodash"; - -import {FlowActions} from "../../actions.js"; -import {RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion} from "../../flow/utils.js"; -import {Key, formatTimeStamp} from "../../utils.js"; -import ContentView from "./contentview.js"; -import {ValueEditor} from "../editor.js"; - -var Headers = React.createClass({ - propTypes: { - onChange: React.PropTypes.func.isRequired, - message: React.PropTypes.object.isRequired - }, - onChange: function (row, col, val) { - var nextHeaders = _.cloneDeep(this.props.message.headers); - nextHeaders[row][col] = val; - if (!nextHeaders[row][0] && !nextHeaders[row][1]) { - // do not delete last row - if (nextHeaders.length === 1) { - nextHeaders[0][0] = "Name"; - nextHeaders[0][1] = "Value"; - } else { - nextHeaders.splice(row, 1); - // manually move selection target if this has been the last row. - if (row === nextHeaders.length) { - this._nextSel = (row - 1) + "-value"; - } - } - } - this.props.onChange(nextHeaders); - }, - edit: function () { - this.refs["0-key"].focus(); - }, - onTab: function (row, col, e) { - var headers = this.props.message.headers; - if (row === headers.length - 1 && col === 1) { - e.preventDefault(); - - var nextHeaders = _.cloneDeep(this.props.message.headers); - nextHeaders.push(["Name", "Value"]); - this.props.onChange(nextHeaders); - this._nextSel = (row + 1) + "-key"; - } - }, - componentDidUpdate: function () { - if (this._nextSel && this.refs[this._nextSel]) { - this.refs[this._nextSel].focus(); - this._nextSel = undefined; - } - }, - onRemove: function (row, col, e) { - if (col === 1) { - e.preventDefault(); - this.refs[row + "-key"].focus(); - } else if (row > 0) { - e.preventDefault(); - this.refs[(row - 1) + "-value"].focus(); - } - }, - render: function () { - - var rows = this.props.message.headers.map(function (header, i) { - - var kEdit = <HeaderEditor - ref={i + "-key"} - content={header[0]} - onDone={this.onChange.bind(null, i, 0)} - onRemove={this.onRemove.bind(null, i, 0)} - onTab={this.onTab.bind(null, i, 0)}/>; - var vEdit = <HeaderEditor - ref={i + "-value"} - content={header[1]} - onDone={this.onChange.bind(null, i, 1)} - onRemove={this.onRemove.bind(null, i, 1)} - onTab={this.onTab.bind(null, i, 1)}/>; - return ( - <tr key={i}> - <td className="header-name">{kEdit}:</td> - <td className="header-value">{vEdit}</td> - </tr> - ); - }.bind(this)); - return ( - <table className="header-table"> - <tbody> - {rows} - </tbody> - </table> - ); - } -}); - -var HeaderEditor = React.createClass({ - render: function () { - return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/>; - }, - focus: function () { - ReactDOM.findDOMNode(this).focus(); - }, - onKeyDown: function (e) { - switch (e.keyCode) { - case Key.BACKSPACE: - var s = window.getSelection().getRangeAt(0); - if (s.startOffset === 0 && s.endOffset === 0) { - this.props.onRemove(e); - } - break; - case Key.TAB: - if (!e.shiftKey) { - this.props.onTab(e); - } - break; - } - } -}); - -var RequestLine = React.createClass({ - render: function () { - var flow = this.props.flow; - var url = RequestUtils.pretty_url(flow.request); - var httpver = flow.request.http_version; - - return <div className="first-line request-line"> - <ValueEditor - ref="method" - content={flow.request.method} - onDone={this.onMethodChange} - inline/> - - <ValueEditor - ref="url" - content={url} - onDone={this.onUrlChange} - isValid={this.isValidUrl} - inline/> - - <ValueEditor - ref="httpVersion" - content={httpver} - onDone={this.onHttpVersionChange} - isValid={isValidHttpVersion} - inline/> - </div> - }, - isValidUrl: function (url) { - var u = parseUrl(url); - return !!u.host; - }, - onMethodChange: function (nextMethod) { - FlowActions.update( - this.props.flow, - {request: {method: nextMethod}} - ); - }, - onUrlChange: function (nextUrl) { - var props = parseUrl(nextUrl); - props.path = props.path || ""; - FlowActions.update( - this.props.flow, - {request: props} - ); - }, - onHttpVersionChange: function (nextVer) { - var ver = parseHttpVersion(nextVer); - FlowActions.update( - this.props.flow, - {request: {http_version: ver}} - ); - } -}); - -var ResponseLine = React.createClass({ - render: function () { - var flow = this.props.flow; - var httpver = flow.response.http_version; - return <div className="first-line response-line"> - <ValueEditor - ref="httpVersion" - content={httpver} - onDone={this.onHttpVersionChange} - isValid={isValidHttpVersion} - inline/> - - <ValueEditor - ref="code" - content={flow.response.status_code + ""} - onDone={this.onCodeChange} - isValid={this.isValidCode} - inline/> - - <ValueEditor - ref="msg" - content={flow.response.reason} - onDone={this.onMsgChange} - inline/> - </div>; - }, - isValidCode: function (code) { - return /^\d+$/.test(code); - }, - onHttpVersionChange: function (nextVer) { - var ver = parseHttpVersion(nextVer); - FlowActions.update( - this.props.flow, - {response: {http_version: ver}} - ); - }, - onMsgChange: function (nextMsg) { - FlowActions.update( - this.props.flow, - {response: {msg: nextMsg}} - ); - }, - onCodeChange: function (nextCode) { - nextCode = parseInt(nextCode); - FlowActions.update( - this.props.flow, - {response: {code: nextCode}} - ); - } -}); - -export var Request = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( - <section className="request"> - <RequestLine ref="requestLine" flow={flow}/> - {/*<ResponseLine flow={flow}/>*/} - <Headers ref="headers" message={flow.request} onChange={this.onHeaderChange}/> - <hr/> - <ContentView flow={flow} message={flow.request}/> - </section> - ); - }, - edit: function (k) { - switch (k) { - case "m": - this.refs.requestLine.refs.method.focus(); - break; - case "u": - this.refs.requestLine.refs.url.focus(); - break; - case "v": - this.refs.requestLine.refs.httpVersion.focus(); - break; - case "h": - this.refs.headers.edit(); - break; - default: - throw "Unimplemented: " + k; - } - }, - onHeaderChange: function (nextHeaders) { - FlowActions.update(this.props.flow, { - request: { - headers: nextHeaders - } - }); - } -}); - -export var Response = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( - <section className="response"> - {/*<RequestLine flow={flow}/>*/} - <ResponseLine ref="responseLine" flow={flow}/> - <Headers ref="headers" message={flow.response} onChange={this.onHeaderChange}/> - <hr/> - <ContentView flow={flow} message={flow.response}/> - </section> - ); - }, - edit: function (k) { - switch (k) { - case "c": - this.refs.responseLine.refs.status_code.focus(); - break; - case "m": - this.refs.responseLine.refs.msg.focus(); - break; - case "v": - this.refs.responseLine.refs.httpVersion.focus(); - break; - case "h": - this.refs.headers.edit(); - break; - default: - throw "Unimplemented: " + k; - } - }, - onHeaderChange: function (nextHeaders) { - FlowActions.update(this.props.flow, { - response: { - headers: nextHeaders - } - }); - } -}); - -export var Error = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( - <section> - <div className="alert alert-warning"> - {flow.error.msg} - <div> - <small>{ formatTimeStamp(flow.error.timestamp) }</small> - </div> - </div> - </section> - ); - } -}); diff --git a/web/src/js/components/flowview/nav.js b/web/src/js/components/flowview/nav.js deleted file mode 100644 index a12fd1fd..00000000 --- a/web/src/js/components/flowview/nav.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; - -import {FlowActions} from "../../actions.js"; - -var NavAction = React.createClass({ - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); - }, - render: function () { - return ( - <a title={this.props.title} - href="#" - className="nav-action" - onClick={this.onClick}> - <i className={"fa fa-fw " + this.props.icon}></i> - </a> - ); - } -}); - -var Nav = React.createClass({ - render: function () { - var flow = this.props.flow; - - var tabs = this.props.tabs.map(function (e) { - var str = e.charAt(0).toUpperCase() + e.slice(1); - var className = this.props.active === e ? "active" : ""; - var onClick = function (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return <a key={e} - href="#" - className={className} - onClick={onClick}>{str}</a>; - }.bind(this)); - - var acceptButton = null; - if(flow.intercepted){ - acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={FlowActions.accept.bind(null, flow)} />; - } - var revertButton = null; - if(flow.modified){ - revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={FlowActions.revert.bind(null, flow)} />; - } - - return ( - <nav ref="head" className="nav-tabs nav-tabs-sm"> - {tabs} - <NavAction title="[d]elete flow" icon="fa-trash" onClick={FlowActions.delete.bind(null, flow)} /> - <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={FlowActions.duplicate.bind(null, flow)} /> - <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={FlowActions.replay.bind(null, flow)} /> - {acceptButton} - {revertButton} - </nav> - ); - } -}); - -export default Nav;
\ No newline at end of file diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js deleted file mode 100644 index 5ab26b82..00000000 --- a/web/src/js/components/prompt.js +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import ReactDOM from 'react-dom'; -import _ from "lodash"; - -import {Key} from "../utils.js"; - -var Prompt = React.createClass({ - contextTypes: { - returnFocus: React.PropTypes.func - }, - propTypes: { - options: React.PropTypes.array.isRequired, - done: React.PropTypes.func.isRequired, - prompt: React.PropTypes.string - }, - componentDidMount: function () { - ReactDOM.findDOMNode(this).focus(); - }, - onKeyDown: function (e) { - e.stopPropagation(); - e.preventDefault(); - var opts = this.getOptions(); - for (var i = 0; i < opts.length; i++) { - var k = opts[i].key; - if (Key[k.toUpperCase()] === e.keyCode) { - this.done(k); - return; - } - } - if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) { - this.done(false); - } - }, - onClick: function (e) { - this.done(false); - }, - done: function (ret) { - this.props.done(ret); - this.context.returnFocus(); - }, - getOptions: function () { - var opts = []; - - var keyTaken = function (k) { - return _.includes(_.map(opts, "key"), k); - }; - - for (var i = 0; i < this.props.options.length; i++) { - var opt = this.props.options[i]; - if (_.isString(opt)) { - var str = opt; - while (str.length > 0 && keyTaken(str[0])) { - str = str.substr(1); - } - opt = { - text: opt, - key: str[0] - }; - } - if (!opt.text || !opt.key || keyTaken(opt.key)) { - throw "invalid options"; - } else { - opts.push(opt); - } - } - return opts; - }, - render: function () { - var opts = this.getOptions(); - opts = _.map(opts, function (o) { - var prefix, suffix; - var idx = o.text.indexOf(o.key); - if (idx !== -1) { - prefix = o.text.substring(0, idx); - suffix = o.text.substring(idx + 1); - - } else { - prefix = o.text + " ("; - suffix = ")"; - } - var onClick = function (e) { - this.done(o.key); - e.stopPropagation(); - }.bind(this); - return <span - key={o.key} - className="option" - onClick={onClick}> - {prefix} - <strong className="text-primary">{o.key}</strong>{suffix} - </span>; - }.bind(this)); - return <div tabIndex="0" onKeyDown={this.onKeyDown} onClick={this.onClick} className="prompt-dialog"> - <div className="prompt-content"> - {this.props.prompt || <strong>Select: </strong> } - {opts} - </div> - </div>; - } -}); - -export default Prompt; |