ngrok.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. var ngrok = angular.module("ngrok", []);
  2. var hexRepr = function(bytes) {
  3. var buf = [];
  4. var ascii = [];
  5. for (var i=0; i<bytes.length; ++i) {
  6. var b = bytes[i];
  7. if (!(i%8) && i!=0) {
  8. buf.push("\t");
  9. buf.push.apply(buf, ascii)
  10. buf.push('\n');
  11. ascii = [];
  12. }
  13. if (b < 16) {
  14. buf.push("0");
  15. }
  16. if (b < 0x20 || b > 0x7e) {
  17. ascii.push('.');
  18. } else {
  19. ascii.push(String.fromCharCode(b));
  20. }
  21. buf.push(b.toString(16));
  22. buf.push(" ");
  23. ascii.push(" ");
  24. }
  25. if (ascii.length > 0) {
  26. var charsLeft = 8 - (ascii.length / 2);
  27. for (i=0; i<charsLeft; ++i) {
  28. buf.push(" ");
  29. }
  30. buf.push("\t");
  31. buf.push.apply(buf, ascii);
  32. }
  33. return buf.join("");
  34. }
  35. ngrok.factory("txnSvc", function() {
  36. var processBody = function(body, binary) {
  37. body.binary = binary;
  38. body.isForm = body.ContentType == "application/x-www-form-urlencoded";
  39. body.exists = body.Length > 0;
  40. body.hasError = !!body.Error;
  41. body.syntaxClass = {
  42. "text/xml": "xml",
  43. "application/xml": "xml",
  44. "text/html": "xml",
  45. "text/css": "css",
  46. "application/json": "json",
  47. "text/javascript": "javascript",
  48. "application/javascript": "javascript",
  49. }[body.ContentType];
  50. // decode body
  51. if (binary) {
  52. body.Text = "";
  53. } else {
  54. body.Text = Base64.decode(body.Text).text;
  55. }
  56. // prettify
  57. var transform = {
  58. "xml": "xml",
  59. "json": "json"
  60. }[body.syntaxClass];
  61. if (!body.hasError && !!transform) {
  62. try {
  63. // vkbeautify does poorly at formatting html
  64. if (body.ContentType != "text/html") {
  65. body.Text = vkbeautify[transform](body.Text);
  66. }
  67. } catch (e) {
  68. }
  69. }
  70. };
  71. var processReq = function(req) {
  72. if (!req.RawBytes) {
  73. var decoded = Base64.decode(req.Raw);
  74. req.RawBytes = hexRepr(decoded.bytes);
  75. if (!req.Binary) {
  76. req.RawText = decoded.text;
  77. }
  78. }
  79. processBody(req.Body, req.Binary);
  80. };
  81. var processResp = function(resp) {
  82. resp.statusClass = {
  83. '2': "text-info",
  84. '3': "muted",
  85. '4': "text-warning",
  86. '5': "text-error"
  87. }[resp.Status[0]];
  88. if (!resp.RawBytes) {
  89. var decoded = Base64.decode(resp.Raw);
  90. resp.RawBytes = hexRepr(decoded.bytes);
  91. if (!resp.Binary) {
  92. resp.RawText = decoded.text;
  93. }
  94. }
  95. processBody(resp.Body, resp.Binary);
  96. };
  97. var processTxn = function(txn) {
  98. processReq(txn.Req);
  99. processResp(txn.Resp);
  100. };
  101. var preprocessTxn = function(txn) {
  102. var toFixed = function(value, precision) {
  103. var power = Math.pow(10, precision || 0);
  104. return String(Math.round(value * power) / power);
  105. }
  106. // parse nanosecond count
  107. var ns = txn.Duration;
  108. var ms = ns / (1000 * 1000);
  109. txn.Duration = ms;
  110. if (ms > 1000) {
  111. txn.Duration = toFixed(ms / 1000, 2) + "s";
  112. } else {
  113. txn.Duration = toFixed(ms, 2) + "ms";
  114. }
  115. };
  116. var active;
  117. var txns = window.data.Txns;
  118. txns.forEach(function(t) {
  119. preprocessTxn(t);
  120. });
  121. var activate = function(txn) {
  122. if (!txn.processed) {
  123. processTxn(txn);
  124. txn.processed = true;
  125. }
  126. active = txn;
  127. }
  128. if (txns.length > 0) {
  129. activate(txns[0]);
  130. }
  131. return {
  132. add: function(txnData) {
  133. txns.unshift(JSON.parse(txnData));
  134. preprocessTxn(txns[0]);
  135. if (!active) {
  136. activate(txns[0]);
  137. }
  138. },
  139. all: function() {
  140. return txns;
  141. },
  142. active: function(txn) {
  143. if (!txn) {
  144. return active;
  145. } else {
  146. activate(txn);
  147. }
  148. },
  149. isActive: function(txn) {
  150. return !!active && txn.Id == active.Id;
  151. }
  152. };
  153. });
  154. ngrok.directive({
  155. "keyval": function() {
  156. return {
  157. scope: {
  158. title: "@",
  159. tuples: "=",
  160. },
  161. replace: true,
  162. restrict: "E",
  163. template: "" +
  164. '<div ng-show="hasKeys()">' +
  165. '<h6>{{title}}</h6>' +
  166. '<table class="table params">' +
  167. '<tr ng-repeat="(key, value) in tuples">' +
  168. '<th>{{ key }}</th>' +
  169. '<td>{{ value }}</td>' +
  170. '</tr>' +
  171. '</table>' +
  172. '</div>',
  173. link: function($scope) {
  174. $scope.hasKeys = function() {
  175. for (key in $scope.tuples) { return true; }
  176. return false;
  177. };
  178. }
  179. };
  180. },
  181. "tabs": function() {
  182. return {
  183. scope: {
  184. "tabs": "@",
  185. "btn": "@",
  186. "onbtnclick": "&"
  187. },
  188. replace: true,
  189. template: '' +
  190. '<ul class="nav nav-pills">' +
  191. '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
  192. '<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
  193. '</li>' +
  194. '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
  195. '</ul>',
  196. link: function postLink(scope, element, attrs) {
  197. scope.tabNames = attrs.tabs.split(",");
  198. scope.activeTab = scope.tabNames[0];
  199. scope.setTab = function(t) {
  200. scope.activeTab = t;
  201. };
  202. scope.$parent.isTab = scope.isTab = function(t) {
  203. return t == scope.activeTab;
  204. };
  205. },
  206. };
  207. },
  208. "body": function() {
  209. return {
  210. scope: {
  211. "body": "=",
  212. "binary": "="
  213. },
  214. template: '' +
  215. '<h6 ng-show="body.exists">' +
  216. '{{ body.Length }} bytes ' +
  217. '{{ body.RawContentType }}' +
  218. '</h6>' +
  219. '' +
  220. '<div ng-show="!body.isForm && !body.binary">' +
  221. '<pre ng-show="body.exists"><code ng-class="body.syntaxClass">{{ body.Text }}</code></pre>' +
  222. '</div>' +
  223. '' +
  224. '<div ng-show="body.isForm">' +
  225. '<keyval title="Form Params" tuples="body.Form">' +
  226. '</div>' +
  227. '<div ng-show="body.hasError" class="alert">' +
  228. '{{ body.Error }}' +
  229. '</div>',
  230. link: function($scope, $elem) {
  231. $scope.$watch(function() { return $scope.body; }, function() {
  232. $code = $elem.find("code");
  233. // if we highlight when the code is empty, hljs manipulates the dom in a
  234. // a bad way that causes angular to fail
  235. if (!!$code.text()) {
  236. hljs.highlightBlock($code.get(0));
  237. }
  238. if ($scope.body && $scope.body.ErrorOffset > -1) {
  239. var offset = $scope.body.ErrorOffset;
  240. function textNodes(node) {
  241. var textNodes = [];
  242. function getTextNodes(node) {
  243. if (node.nodeType == 3) {
  244. textNodes.push(node);
  245. } else {
  246. for (var i = 0, len = node.childNodes.length; i < len; ++i) {
  247. getTextNodes(node.childNodes[i]);
  248. }
  249. }
  250. }
  251. getTextNodes(node);
  252. return textNodes;
  253. }
  254. var tNodes = textNodes($elem.find("code").get(0));
  255. for (var i=0; i<tNodes.length; i++) {
  256. offset -= tNodes[i].nodeValue.length;
  257. if (offset < 0) {
  258. $(tNodes[i]).parent().css("background-color", "orange");
  259. break;
  260. }
  261. }
  262. }
  263. });
  264. }
  265. };
  266. }
  267. });
  268. ngrok.controller({
  269. "HttpTxns": function($scope, txnSvc) {
  270. $scope.publicUrl = window.data.UiState.Url;
  271. $scope.txns = txnSvc.all();
  272. if (!!window.WebSocket) {
  273. var ws = new WebSocket("ws://localhost:4040/_ws");
  274. ws.onopen = function() {
  275. console.log("connected websocket for real-time updates");
  276. };
  277. ws.onmessage = function(message) {
  278. $scope.$apply(function() {
  279. txnSvc.add(message.data);
  280. });
  281. };
  282. ws.onerror = function(err) {
  283. console.log("Web socket error:" + err);
  284. };
  285. ws.onclose = function(cls) {
  286. console.log("Web socket closed:" + cls);
  287. };
  288. }
  289. },
  290. "HttpRequest": function($scope, txnSvc) {
  291. $scope.replay = function() {
  292. $.ajax({
  293. type: "POST",
  294. url: "/http/in/replay",
  295. data: { txnid: txnSvc.active().Id }
  296. });
  297. }
  298. var setReq = function() {
  299. var txn = txnSvc.active();
  300. if (!!txn && txn.Req) {
  301. $scope.Req = txnSvc.active().Req;
  302. } else {
  303. $scope.Req = null;
  304. }
  305. };
  306. $scope.$watch(function() { return txnSvc.active() }, setReq);
  307. },
  308. "HttpResponse": function($scope, $element, $timeout, txnSvc) {
  309. var setResp = function() {
  310. var txn = txnSvc.active();
  311. if (!!txn && txn.Resp) {
  312. $scope.Resp = txnSvc.active().Resp;
  313. } else {
  314. $scope.Resp = null;
  315. }
  316. };
  317. $scope.$watch(function() { return txnSvc.active() }, setResp);
  318. },
  319. "TxnNavItem": function($scope, txnSvc) {
  320. $scope.isActive = function() { return txnSvc.isActive($scope.txn); }
  321. $scope.makeActive = function() {
  322. txnSvc.active($scope.txn);
  323. };
  324. },
  325. });