web-console 3.6.2 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,11 +5,12 @@ require "rails/railtie"
5
5
  module WebConsole
6
6
  class Railtie < ::Rails::Railtie
7
7
  config.web_console = ActiveSupport::OrderedOptions.new
8
- config.web_console.whitelisted_ips = %w( 127.0.0.1 ::1 )
9
8
 
10
9
  initializer "web_console.initialize" do
11
10
  require "bindex"
12
11
  require "web_console/extensions"
12
+
13
+ ActionDispatch::DebugExceptions.register_interceptor(Interceptor)
13
14
  end
14
15
 
15
16
  initializer "web_console.development_only" do
@@ -50,9 +51,23 @@ module WebConsole
50
51
  end
51
52
  end
52
53
 
53
- initializer "web_console.whitelisted_ips" do
54
- if whitelisted_ips = config.web_console.whitelisted_ips
55
- Request.whitelisted_ips = Whitelist.new(whitelisted_ips)
54
+ initializer "web_console.permissions" do
55
+ permissions = web_console_permissions
56
+ Request.permissions = Permissions.new(permissions)
57
+ end
58
+
59
+ def web_console_permissions
60
+ case
61
+ when config.web_console.permissions
62
+ config.web_console.permissions
63
+ when config.web_console.allowed_ips
64
+ config.web_console.allowed_ips
65
+ when config.web_console.whitelisted_ips
66
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
67
+ The config.web_console.whitelisted_ips is deprecated and will be ignored in future release of web_console.
68
+ Please use config.web_console.allowed_ips instead.
69
+ MSG
70
+ config.web_console.whitelisted_ips
56
71
  end
57
72
  end
58
73
 
@@ -1,35 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebConsole
4
- # Web Console tailored request object.
5
4
  class Request < ActionDispatch::Request
6
- # Configurable set of whitelisted networks.
7
- cattr_accessor :whitelisted_ips
8
- @@whitelisted_ips = Whitelist.new
5
+ cattr_accessor :permissions, default: Permissions.new
9
6
 
10
- # Define a vendor MIME type. We can call it using Mime[:web_console_v2].
11
- Mime::Type.register "application/vnd.web-console.v2", :web_console_v2
12
-
13
- # Returns whether a request came from a whitelisted IP.
14
- #
15
- # For a request to hit Web Console features, it needs to come from a white
16
- # listed IP.
17
- def from_whitelisted_ip?
18
- whitelisted_ips.include?(strict_remote_ip)
7
+ def permitted?
8
+ permissions.include?(strict_remote_ip)
19
9
  end
20
10
 
21
- # Determines the remote IP using our much stricter whitelist.
22
11
  def strict_remote_ip
23
- GetSecureIp.new(self, whitelisted_ips).to_s
12
+ GetSecureIp.new(self, permissions).to_s
24
13
  rescue ActionDispatch::RemoteIp::IpSpoofAttackError
25
14
  "[Spoofed]"
26
15
  end
27
16
 
28
- # Returns whether the request is acceptable.
29
- def acceptable?
30
- xhr? && accepts.any? { |mime| Mime[:web_console_v2] == mime }
31
- end
32
-
33
17
  private
34
18
 
35
19
  class GetSecureIp < ActionDispatch::RemoteIp::GetIp
@@ -11,8 +11,7 @@ module WebConsole
11
11
  # error pages only, as currently, this is the only client that needs to do
12
12
  # that.
13
13
  class Session
14
- cattr_reader :inmemory_storage
15
- @@inmemory_storage = {}
14
+ cattr_reader :inmemory_storage, default: {}
16
15
 
17
16
  class << self
18
17
  # Finds a persisted session in memory by its id.
@@ -32,9 +31,9 @@ module WebConsole
32
31
  # storage.
33
32
  def from(storage)
34
33
  if exc = storage[:__web_console_exception]
35
- new(ExceptionMapper.new(exc))
34
+ new(ExceptionMapper.follow(exc))
36
35
  elsif binding = storage[:__web_console_binding]
37
- new([binding])
36
+ new([[binding]])
38
37
  end
39
38
  end
40
39
  end
@@ -42,10 +41,11 @@ module WebConsole
42
41
  # An unique identifier for every REPL.
43
42
  attr_reader :id
44
43
 
45
- def initialize(bindings)
44
+ def initialize(exception_mappers)
46
45
  @id = SecureRandom.hex(16)
47
- @bindings = bindings
48
- @evaluator = Evaluator.new(@current_binding = bindings.first)
46
+
47
+ @exception_mappers = exception_mappers
48
+ @evaluator = Evaluator.new(@current_binding = exception_mappers.first.first)
49
49
 
50
50
  store_into_memory
51
51
  end
@@ -60,8 +60,10 @@ module WebConsole
60
60
  # Switches the current binding to the one at specified +index+.
61
61
  #
62
62
  # Returns nothing.
63
- def switch_binding_to(index)
64
- @evaluator = Evaluator.new(@current_binding = @bindings[index.to_i])
63
+ def switch_binding_to(index, exception_object_id)
64
+ bindings = ExceptionMapper.find_binding(@exception_mappers, exception_object_id)
65
+
66
+ @evaluator = Evaluator.new(@current_binding = bindings[index.to_i])
65
67
  end
66
68
 
67
69
  # Returns context of the current binding
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SourceLocation
4
+ def initialize(binding)
5
+ @binding = binding
6
+ end
7
+
8
+ if RUBY_VERSION >= "2.6"
9
+ def path() @binding.source_location.first end
10
+ def lineno() @binding.source_location.last end
11
+ else
12
+ def path() @binding.eval("__FILE__") end
13
+ def lineno() @binding.eval("__LINE__") end
14
+ end
15
+ end
@@ -7,8 +7,7 @@ module WebConsole
7
7
  # Rails error pages.
8
8
  class Template
9
9
  # Lets you customize the default templates folder location.
10
- cattr_accessor :template_paths
11
- @@template_paths = [ File.expand_path("../templates", __FILE__) ]
10
+ cattr_accessor :template_paths, default: [ File.expand_path("../templates", __FILE__) ]
12
11
 
13
12
  def initialize(env, session)
14
13
  @env = env
@@ -18,7 +17,7 @@ module WebConsole
18
17
 
19
18
  # Render a template (inferred from +template_paths+) as a plain string.
20
19
  def render(template)
21
- view = View.new(template_paths, instance_values)
20
+ view = View.new(ActionView::LookupContext.new(template_paths), instance_values)
22
21
  view.render(template: template, layout: false)
23
22
  end
24
23
  end
@@ -380,8 +380,13 @@ REPLConsole.prototype.install = function(container) {
380
380
  var clientHeightStart = consoleOuter.clientHeight;
381
381
 
382
382
  var doDrag = function(e) {
383
- container.style.height = (startHeight + startY - e.clientY) + 'px';
383
+ var height = startHeight + startY - e.clientY;
384
384
  consoleOuter.scrollTop = scrollTopStart + (clientHeightStart - consoleOuter.clientHeight);
385
+ if (height > document.documentElement.clientHeight) {
386
+ container.style.height = document.documentElement.clientHeight;
387
+ } else {
388
+ container.style.height = height + 'px';
389
+ }
385
390
  shiftConsoleActions();
386
391
  };
387
392
 
@@ -603,6 +608,12 @@ REPLConsole.prototype.writeError = function(output) {
603
608
  return consoleMessage;
604
609
  };
605
610
 
611
+ REPLConsole.prototype.writeNotification = function(output) {
612
+ var consoleMessage = this.writeOutput(output);
613
+ addClass(consoleMessage, "notification-message");
614
+ return consoleMessage;
615
+ };
616
+
606
617
  REPLConsole.prototype.onEnterKey = function() {
607
618
  var input = this._input;
608
619
 
@@ -654,57 +665,85 @@ REPLConsole.prototype.onKeyDown = function(ev) {
654
665
  }
655
666
 
656
667
  switch (ev.keyCode) {
657
- case 69:
658
- // Ctrl-E
668
+ case 65: // Ctrl-A
669
+ if (ev.ctrlKey) {
670
+ this.setInput(this._input, 0);
671
+ ev.preventDefault();
672
+ }
673
+ break;
674
+
675
+ case 69: // Ctrl-E
659
676
  if (ev.ctrlKey) {
660
677
  this.onTabKey();
661
678
  ev.preventDefault();
662
679
  }
663
680
  break;
664
- case 9:
665
- // Tab
681
+
682
+ case 87: // Ctrl-W
683
+ if (ev.ctrlKey) {
684
+ this.deleteWord();
685
+ ev.preventDefault();
686
+ }
687
+ break;
688
+
689
+ case 85: // Ctrl-U
690
+ if (ev.ctrlKey) {
691
+ this.deleteLine();
692
+ ev.preventDefault();
693
+ }
694
+ break;
695
+
696
+ case 69: // Ctrl-E
697
+ if (ev.ctrlKey) {
698
+ this.onTabKey();
699
+ ev.preventDefault();
700
+ }
701
+ break;
702
+
703
+ case 80: // Ctrl-P
704
+ if (! ev.ctrlKey) break;
705
+
706
+ case 78: // Ctrl-N
707
+ if (! ev.ctrlKey) break;
708
+
709
+ case 9: // Tab
666
710
  this.onTabKey();
667
711
  ev.preventDefault();
668
712
  break;
669
- case 13:
670
- // Enter key
713
+
714
+ case 13: // Enter key
671
715
  this.onEnterKey();
672
716
  ev.preventDefault();
673
717
  break;
674
- case 80:
675
- // Ctrl-P
676
- if (! ev.ctrlKey) break;
677
- case 38:
678
- // Up arrow
718
+
719
+ case 38: // Up arrow
679
720
  this.onNavigateHistory(-1);
680
721
  ev.preventDefault();
681
722
  break;
682
- case 78:
683
- // Ctrl-N
684
- if (! ev.ctrlKey) break;
685
- case 40:
686
- // Down arrow
723
+
724
+ case 40: // Down arrow
687
725
  this.onNavigateHistory(1);
688
726
  ev.preventDefault();
689
727
  break;
690
- case 37:
691
- // Left arrow
728
+
729
+ case 37: // Left arrow
692
730
  var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
693
731
  this.setInput(this._input, caretPos);
694
732
  ev.preventDefault();
695
733
  break;
696
- case 39:
697
- // Right arrow
734
+
735
+ case 39: // Right arrow
698
736
  var length = this._input.length;
699
737
  var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
700
738
  this.setInput(this._input, caretPos);
701
739
  ev.preventDefault();
702
740
  break;
703
- case 8:
704
- // Delete
741
+
742
+ case 8: // Delete
705
743
  this.deleteAtCurrent();
706
744
  ev.preventDefault();
707
745
  break;
746
+
708
747
  default:
709
748
  break;
710
749
  }
@@ -721,7 +760,7 @@ REPLConsole.prototype.onKeyDown = function(ev) {
721
760
  _this.addToInput(_this.clipboard.value);
722
761
  _this.clipboard.value = "";
723
762
  _this.clipboard.blur();
724
- }, 10);
763
+ }, 100);
725
764
  }
726
765
  }
727
766
 
@@ -757,6 +796,47 @@ REPLConsole.prototype.deleteAtCurrent = function() {
757
796
  }
758
797
  };
759
798
 
799
+ /**
800
+ * Deletes the current line.
801
+ */
802
+ REPLConsole.prototype.deleteLine = function() {
803
+ if (this._caretPos > 0) {
804
+ this.setInput("", 0);
805
+
806
+ if (!this._input) {
807
+ this.autocomplete && this.autocomplete.cancel();
808
+ this.autocomplete = false;
809
+ }
810
+ }
811
+ };
812
+
813
+ /**
814
+ * Deletes the current word.
815
+ */
816
+ REPLConsole.prototype.deleteWord = function() {
817
+ if (this._caretPos > 0) {
818
+ var i = 1, current = this._caretPos;
819
+ while (this._input[current - i++] == " ");
820
+
821
+ var deleteIndex = 0;
822
+ for (; current - i > 0; i++) {
823
+ if (this._input[current - i] == " ") {
824
+ deleteIndex = current - i;
825
+ break;
826
+ }
827
+ }
828
+
829
+ var before = this._input.substring(0, deleteIndex);
830
+ var after = this._input.substring(current, this._input.length);
831
+ this.setInput(before + after, deleteIndex);
832
+
833
+ if (!this._input) {
834
+ this.autocomplete && this.autocomplete.cancel();
835
+ this.autocomplete = false;
836
+ }
837
+ }
838
+ };
839
+
760
840
  /**
761
841
  * Insert a character at the current position.
762
842
  */
@@ -795,11 +875,20 @@ REPLConsole.prototype.scrollToBottom = function() {
795
875
  this.outer.scrollTop = this.outer.scrollHeight;
796
876
  };
797
877
 
798
- // Change the binding of the console
799
- REPLConsole.prototype.switchBindingTo = function(frameId, callback) {
878
+ // Change the binding of the console.
879
+ REPLConsole.prototype.switchBindingTo = function(frameId, exceptionObjectId, callback) {
800
880
  var url = this.getSessionUrl('trace');
801
881
  var params = "frame_id=" + encodeURIComponent(frameId);
802
- postRequest(url, params, callback);
882
+
883
+ if (exceptionObjectId) {
884
+ params = params + "&exception_object_id=" + encodeURIComponent(exceptionObjectId);
885
+ }
886
+
887
+ var _this = this;
888
+ postRequest(url, params, function() {
889
+ var text = "Context has changed to: " + callback();
890
+ _this.writeNotification(text);
891
+ });
803
892
  };
804
893
 
805
894
  /**
@@ -835,7 +924,6 @@ REPLConsole.request = function request(method, url, params, callback) {
835
924
  xhr.open(method, url, true);
836
925
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
837
926
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
838
- xhr.setRequestHeader("Accept", "<%= Mime[:web_console_v2] %>");
839
927
  xhr.send(params);
840
928
 
841
929
  xhr.onreadystatechange = function() {
@@ -8,15 +8,13 @@ for (var i = 0; i < traceFrames.length; i++) {
8
8
  e.preventDefault();
9
9
  var target = e.target;
10
10
  var frameId = target.dataset.frameId;
11
+ var exceptionObjectId = target.dataset.exceptionObjectId;
11
12
 
12
13
  // Change the binding of the console.
13
- changeBinding(frameId, function() {
14
- if (selectedFrame) {
15
- selectedFrame.className = selectedFrame.className.replace("selected", "");
16
- }
17
-
18
- target.className += " selected";
14
+ changeBinding(frameId, exceptionObjectId, function() {
15
+ // Rails already handles toggling the select class
19
16
  selectedFrame = target;
17
+ return target.innerHTML;
20
18
  });
21
19
 
22
20
  // Change the extracted source code
@@ -24,8 +22,9 @@ for (var i = 0; i < traceFrames.length; i++) {
24
22
  });
25
23
  }
26
24
 
27
- function changeBinding(frameId, callback) {
28
- REPLConsole.currentSession.switchBindingTo(frameId, callback);
25
+ // Change the binding of the current session and prompt the user.
26
+ function changeBinding(frameId, exceptionObjectId, callback) {
27
+ REPLConsole.currentSession.switchBindingTo(frameId, exceptionObjectId, callback);
29
28
  }
30
29
 
31
30
  function changeSourceExtract(frameId) {
@@ -6,3 +6,7 @@
6
6
  <% only_on_error_page do %>
7
7
  <%= render_javascript 'error_page' %>
8
8
  <% end %>
9
+
10
+ <% only_on_regular_page do %>
11
+ <%= render_javascript 'regular_page' %>
12
+ <% end %>
@@ -0,0 +1,24 @@
1
+ // Push the error page body upwards the size of the console.
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ var consoleElement = document.getElementById('console');
4
+ var resizerElement = consoleElement.getElementsByClassName('resizer')[0];
5
+ var bodyElement = document.body;
6
+
7
+ function setBodyElementBottomMargin(pixels) {
8
+ bodyElement.style.marginBottom = pixels + 'px';
9
+ }
10
+
11
+ var currentConsoleElementHeight = consoleElement.offsetHeight;
12
+ setBodyElementBottomMargin(currentConsoleElementHeight);
13
+
14
+ resizerElement.addEventListener('mousedown', function(event) {
15
+ function recordConsoleElementHeight(event) {
16
+ resizerElement.removeEventListener('mouseup', recordConsoleElementHeight);
17
+
18
+ var currentConsoleElementHeight = consoleElement.offsetHeight;
19
+ setBodyElementBottomMargin(currentConsoleElementHeight);
20
+ }
21
+
22
+ resizerElement.addEventListener('mouseup', recordConsoleElementHeight);
23
+ });
24
+ });