ngrok.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. var ngrok = angular.module("ngrok", ["ngSanitize"]);
  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. var 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. }[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. if (!!syntaxClass) {
  71. body.Text = hljs.highlight(syntaxClass, body.Text).value;
  72. } else {
  73. // highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function.
  74. body.Text = body.Text.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
  75. }
  76. };
  77. var processReq = function(req) {
  78. if (!req.RawBytes) {
  79. var decoded = Base64.decode(req.Raw);
  80. req.RawBytes = hexRepr(decoded.bytes);
  81. if (!req.Binary) {
  82. req.RawText = decoded.text;
  83. }
  84. }
  85. processBody(req.Body, req.Binary);
  86. };
  87. var processResp = function(resp) {
  88. resp.statusClass = {
  89. '2': "text-info",
  90. '3': "muted",
  91. '4': "text-warning",
  92. '5': "text-error"
  93. }[resp.Status[0]];
  94. if (!resp.RawBytes) {
  95. var decoded = Base64.decode(resp.Raw);
  96. resp.RawBytes = hexRepr(decoded.bytes);
  97. if (!resp.Binary) {
  98. resp.RawText = decoded.text;
  99. }
  100. }
  101. processBody(resp.Body, resp.Binary);
  102. };
  103. var processTxn = function(txn) {
  104. processReq(txn.Req);
  105. processResp(txn.Resp);
  106. };
  107. var preprocessTxn = function(txn) {
  108. var toFixed = function(value, precision) {
  109. var power = Math.pow(10, precision || 0);
  110. return String(Math.round(value * power) / power);
  111. }
  112. // parse nanosecond count
  113. var ns = txn.Duration;
  114. var ms = ns / (1000 * 1000);
  115. txn.Duration = ms;
  116. if (ms > 1000) {
  117. txn.Duration = toFixed(ms / 1000, 2) + "s";
  118. } else {
  119. txn.Duration = toFixed(ms, 2) + "ms";
  120. }
  121. };
  122. var active;
  123. var txns = window.data.Txns;
  124. txns.forEach(function(t) {
  125. preprocessTxn(t);
  126. });
  127. var activate = function(txn) {
  128. if (!txn.processed) {
  129. processTxn(txn);
  130. txn.processed = true;
  131. }
  132. active = txn;
  133. }
  134. if (txns.length > 0) {
  135. activate(txns[0]);
  136. }
  137. return {
  138. add: function(txnData) {
  139. txns.unshift(JSON.parse(txnData));
  140. preprocessTxn(txns[0]);
  141. if (!active) {
  142. activate(txns[0]);
  143. }
  144. },
  145. all: function() {
  146. return txns;
  147. },
  148. active: function(txn) {
  149. if (!txn) {
  150. return active;
  151. } else {
  152. activate(txn);
  153. }
  154. },
  155. isActive: function(txn) {
  156. return !!active && txn.Id == active.Id;
  157. }
  158. };
  159. });
  160. ngrok.directive({
  161. "keyval": function() {
  162. return {
  163. scope: {
  164. title: "@",
  165. tuples: "=",
  166. },
  167. replace: true,
  168. restrict: "E",
  169. template: "" +
  170. '<div ng-show="hasKeys()">' +
  171. '<h6>{{title}}</h6>' +
  172. '<table class="table params">' +
  173. '<tr ng-repeat="(key, value) in tuples">' +
  174. '<th>{{ key }}</th>' +
  175. '<td>{{ value }}</td>' +
  176. '</tr>' +
  177. '</table>' +
  178. '</div>',
  179. link: function($scope) {
  180. $scope.hasKeys = function() {
  181. for (key in $scope.tuples) { return true; }
  182. return false;
  183. };
  184. }
  185. };
  186. },
  187. "tabs": function() {
  188. return {
  189. scope: {
  190. "tabs": "@",
  191. "btn": "@",
  192. "onbtnclick": "&"
  193. },
  194. replace: true,
  195. template: '' +
  196. '<ul class="nav nav-pills">' +
  197. '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
  198. '<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
  199. '</li>' +
  200. '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
  201. '</ul>',
  202. link: function postLink(scope, element, attrs) {
  203. scope.tabNames = attrs.tabs.split(",");
  204. scope.activeTab = scope.tabNames[0];
  205. scope.setTab = function(t) {
  206. scope.activeTab = t;
  207. };
  208. scope.$parent.isTab = scope.isTab = function(t) {
  209. return t == scope.activeTab;
  210. };
  211. },
  212. };
  213. },
  214. "body": function() {
  215. return {
  216. scope: {
  217. "body": "=",
  218. "binary": "="
  219. },
  220. template: '' +
  221. '<h6 ng-show="body.exists">' +
  222. '{{ body.Length }} bytes ' +
  223. '{{ body.RawContentType }}' +
  224. '</h6>' +
  225. '' +
  226. '<div ng-show="!body.isForm && !body.binary">' +
  227. '<pre ng-show="body.exists"><code ng-bind-html="body.Text"></code></pre>' +
  228. '</div>' +
  229. '' +
  230. '<div ng-show="body.isForm">' +
  231. '<keyval title="Form Params" tuples="body.Form">' +
  232. '</div>' +
  233. '<div ng-show="body.hasError" class="alert">' +
  234. '{{ body.Error }}' +
  235. '</div>',
  236. link: function($scope, $elem) {
  237. $scope.$watch(function() { return $scope.body; }, function() {
  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.tunnels = window.data.UiState.Tunnels;
  271. $scope.txns = txnSvc.all();
  272. if (!!window.WebSocket) {
  273. var ws = new WebSocket("ws://" + location.host + "/_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:")
  284. console.log(err);
  285. };
  286. ws.onclose = function(cls) {
  287. console.log("Web socket closed:" + cls);
  288. };
  289. }
  290. },
  291. "HttpRequest": function($scope, txnSvc) {
  292. $scope.replay = function() {
  293. $.ajax({
  294. type: "POST",
  295. url: "/http/in/replay",
  296. data: { txnid: txnSvc.active().Id }
  297. });
  298. }
  299. var setReq = function() {
  300. var txn = txnSvc.active();
  301. if (!!txn && txn.Req) {
  302. $scope.Req = txnSvc.active().Req;
  303. } else {
  304. $scope.Req = null;
  305. }
  306. };
  307. $scope.$watch(function() { return txnSvc.active() }, setReq);
  308. },
  309. "HttpResponse": function($scope, $element, txnSvc) {
  310. var setResp = function() {
  311. var txn = txnSvc.active();
  312. if (!!txn && txn.Resp) {
  313. $scope.Resp = txnSvc.active().Resp;
  314. } else {
  315. $scope.Resp = null;
  316. }
  317. };
  318. $scope.$watch(function() { return txnSvc.active() }, setResp);
  319. },
  320. "TxnNavItem": function($scope, txnSvc) {
  321. $scope.isActive = function() { return txnSvc.isActive($scope.txn); }
  322. $scope.makeActive = function() {
  323. txnSvc.active($scope.txn);
  324. };
  325. },
  326. "HttpTxn": function($scope, txnSvc, $timeout) {
  327. var setTxn = function() {
  328. $scope.Txn = txnSvc.active();
  329. };
  330. $scope.ISO8601 = function(ts) {
  331. if (!!ts) {
  332. return new Date(ts * 1000).toISOString();
  333. }
  334. };
  335. $scope.TimeFormat = function(ts) {
  336. if (!!ts) {
  337. return $.timeago($scope.ISO8601(ts));
  338. }
  339. };
  340. $scope.$watch(function() { return txnSvc.active() }, setTxn);
  341. // this causes angular to update the timestamps
  342. setInterval(function() { $scope.$apply(function() {}); }, 30000);
  343. },
  344. });