ngrok.js 10 KB

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