web-console 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c224d95365158b5d6c2c74a723e0f980d6ec2cc1
4
- data.tar.gz: 96c1809b1e11969901c710bd3d6f316bbc1221c5
3
+ metadata.gz: 02d40821ae813ac4e5893b4c853725833017cb46
4
+ data.tar.gz: 56b2826919677b8a93e6ecedce69a176949d15ee
5
5
  SHA512:
6
- metadata.gz: 43fb7f5387311dde55c6464a64ff9f9498486b7afc35f7c0254c851ce77027d288b190cfdd73c56bfebf7987e87d3f33315f7c626fef97b4da044892dbf135c5
7
- data.tar.gz: 51395dfa02548a44395e5bffa801d3c28a4e95b556fa1982ba7045d279871e1c98de4e0bf1fd1b6a5273348ac5ad3c405bde530ffa394d6becab9ea39cf24bc3
6
+ metadata.gz: 6c6a07f241b5bfad39c7a588ac92bccc8f0786db1b807d3ab94b4ec13464d7d8514493634c88247a868e78e43169094d216da6f01c3559be45af39854a76a0a0
7
+ data.tar.gz: 4b656f08efb441715996f541f94806d9f6249171502fded529041c5fc5cead0a73ce4f155fe246f0c4a36c4a0b54f9bdd65fb3091ed5e9b9bfa86b9d95f3a552
data/CHANGELOG.markdown CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ ## 2.3.0
6
+
7
+ * [#181](https://github.com/rails/web-console/pull/181) Log internal Web Console errors ([@schneems])
8
+ * [#150](https://github.com/rails/web-console/pull/150) Revert #150. ([@gsamokovarov])
9
+
5
10
  ## 2.2.1
6
11
 
7
- * [#150](https://github.com/rails/web-console/pull/150) Change config.development_only default until 4.2.4 is released.
12
+ * [#150](https://github.com/rails/web-console/pull/150) Change config.development_only default until 4.2.4 is released. ([@gsamokovarov])
8
13
 
9
14
  ## 2.2.0
10
15
 
@@ -48,3 +53,4 @@
48
53
  [@parterburn]: https://github.com/parterburn
49
54
  [@sh19910711]: https://github.com/sh19910711
50
55
  [@frenesim]: https://github.com/frenesim
56
+ [@schneems]: https://github.com/schneems
data/README.markdown CHANGED
@@ -161,6 +161,18 @@ end
161
161
  You may wanna check the [templates] folder at the source tree for the files you
162
162
  may override.
163
163
 
164
+ ### config.web_console.mount_point
165
+
166
+ Usually the middleware of _Web Console_ is mounted at `/__web_console`.
167
+ If you wanna change the path for some reasons, you can specify it
168
+ by `config.web_console.mount_point`:
169
+
170
+ ```ruby
171
+ Rails.application.configure do
172
+ config.web_console.mount_point = '/path/to/web_console'
173
+ end
174
+ ```
175
+
164
176
  ## FAQ
165
177
 
166
178
  ### Where did /console go?
data/Rakefile CHANGED
@@ -8,6 +8,8 @@ require 'socket'
8
8
  require 'rake/testtask'
9
9
  require 'tmpdir'
10
10
  require 'securerandom'
11
+ require 'json'
12
+ require 'web_console/testing/erb_precompiler'
11
13
 
12
14
  EXPANDED_CWD = File.expand_path(File.dirname(__FILE__))
13
15
 
@@ -18,40 +20,7 @@ Rake::TestTask.new(:test) do |t|
18
20
  t.verbose = false
19
21
  end
20
22
 
21
- namespace :test do
22
- desc "Run tests for templates"
23
- task :templates => "templates:all"
24
-
25
- namespace :templates do
26
- task :all => [:daemonize, :npm, :rackup, :mocha, :kill]
27
- task :serve => [:npm, :rackup]
28
-
29
- work_dir = Pathname(__FILE__).dirname.join("test/templates")
30
- pid_file = Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid")
31
- server_port = 29292
32
- rackup_opts = "-p #{server_port}"
33
-
34
- task :daemonize do
35
- rackup_opts += " -D -P #{pid_file}"
36
- end
37
-
38
- task :npm do
39
- Dir.chdir(work_dir) { system "npm install --silent" }
40
- end
41
-
42
- task :rackup do
43
- Dir.chdir(work_dir) { system "bundle exec rackup #{rackup_opts}" }
44
- end
45
-
46
- task :mocha do
47
- Dir.chdir(work_dir) { system "$(npm bin)/mocha-phantomjs http://localhost:#{server_port}/html/spec_runner.html" }
48
- end
49
-
50
- task :kill do
51
- system "kill #{File.read pid_file}"
52
- end
53
- end
54
- end
23
+ Dir['lib/web_console/tasks/**/*.rake'].each { |task| load task }
55
24
 
56
25
  Bundler::GemHelper.install_tasks
57
26
 
data/lib/web_console.rb CHANGED
@@ -13,6 +13,8 @@ require 'web_console/template'
13
13
  require 'web_console/middleware'
14
14
  require 'web_console/whitelist'
15
15
  require 'web_console/request'
16
+ require 'web_console/response'
17
+ require 'web_console/view'
16
18
  require 'web_console/whiny_request'
17
19
 
18
20
  module WebConsole
@@ -1,6 +1,9 @@
1
1
  ActionDispatch::DebugExceptions.class_eval do
2
- def render_exception_with_web_console(env, exception)
3
- render_exception_without_web_console(env, exception).tap do
2
+ def render_exception_with_web_console(request, exception)
3
+ render_exception_without_web_console(request, exception).tap do
4
+ # Retain superficial Rails 5 compatibility.
5
+ env = Hash === request ? request : request.env
6
+
4
7
  error = ActionDispatch::ExceptionWrapper.new(env, exception).exception
5
8
 
6
9
  # Get the original exception if ExceptionWrapper decides to follow it.
@@ -0,0 +1,15 @@
1
+ en:
2
+ errors:
3
+ unavailable_session: |
4
+ Session %{id} is is no longer available in memory.
5
+
6
+ If you happen to run on a multi-process server (like Unicorn or Puma) the process
7
+ this request hit doesn't store %{id} in memory. Consider turning the number of
8
+ processes/workers to one (1) or using a different server in development.
9
+
10
+ unacceptable_request: |
11
+ A supported version is expected in the Accept header.
12
+
13
+ connection_refused: |
14
+ Oops! Failed to connect to the Web Console middleware.
15
+ Please make sure a rails development server is running.
@@ -4,60 +4,60 @@ module WebConsole
4
4
  class Middleware
5
5
  TEMPLATES_PATH = File.expand_path('../templates', __FILE__)
6
6
 
7
- DEFAULT_OPTIONS = {
8
- update_re: %r{/repl_sessions/(?<id>.+?)\z},
9
- binding_change_re: %r{/repl_sessions/(?<id>.+?)/trace\z}
10
- }
11
-
12
- UNAVAILABLE_SESSION_MESSAGE = <<-END.strip_heredoc
13
- Session %{id} is is no longer available in memory.
14
-
15
- If you happen to run on a multi-process server (like Unicorn) the process
16
- this request hit doesn't store %{id} in memory.
17
- END
18
-
19
- UNACCEPTABLE_REQUEST_MESSAGE = "A supported version is expected in the Accept header."
7
+ cattr_accessor :mount_point
8
+ @@mount_point = '/__web_console'
20
9
 
21
10
  cattr_accessor :whiny_requests
22
11
  @@whiny_requests = true
23
12
 
24
- def initialize(app, options = {})
25
- @app = app
26
- @options = DEFAULT_OPTIONS.merge(options)
13
+ def initialize(app)
14
+ @app = app
27
15
  end
28
16
 
29
17
  def call(env)
30
- request = create_regular_or_whiny_request(env)
31
- return @app.call(env) unless request.from_whitelited_ip?
32
-
33
- if id = id_for_repl_session_update(request)
34
- return update_repl_session(id, request)
35
- elsif id = id_for_repl_session_stack_frame_change(request)
36
- return change_stack_trace(id, request)
37
- end
18
+ app_exception = catch :app_exception do
19
+ request = create_regular_or_whiny_request(env)
20
+ return @app.call(env) unless request.from_whitelited_ip?
21
+
22
+ if id = id_for_repl_session_update(request)
23
+ return update_repl_session(id, request)
24
+ elsif id = id_for_repl_session_stack_frame_change(request)
25
+ return change_stack_trace(id, request)
26
+ end
38
27
 
39
- status, headers, body = @app.call(env)
28
+ status, headers, body = @app.call(env)
40
29
 
41
- if exception = env['web_console.exception']
42
- session = Session.from_exception(exception)
43
- elsif binding = env['web_console.binding']
44
- session = Session.from_binding(binding)
45
- end
30
+ if exception = env['web_console.exception']
31
+ session = Session.from_exception(exception)
32
+ elsif binding = env['web_console.binding']
33
+ session = Session.from_binding(binding)
34
+ end
46
35
 
47
- if session && request.acceptable_content_type?
48
- headers["X-Web-Console-Session-Id"] = session.id
49
- response = Rack::Response.new(body, status, headers)
50
- template = Template.new(env, session)
36
+ if session && acceptable_content_type?(headers)
37
+ response = Response.new(body, status, headers)
38
+ template = Template.new(env, session)
51
39
 
52
- response.write(template.render('index'))
53
- response.finish
54
- else
55
- [ status, headers, body ]
40
+ response.headers["X-Web-Console-Session-Id"] = session.id
41
+ response.headers["X-Web-Console-Mount-Point"] = mount_point
42
+ response.write(template.render('index'))
43
+ response.finish
44
+ else
45
+ [ status, headers, body ]
46
+ end
56
47
  end
48
+ rescue => e
49
+ WebConsole.logger.error("\n#{e.class}: #{e}\n\tfrom #{e.backtrace.join("\n\tfrom ")}")
50
+ raise e
51
+ ensure
52
+ raise app_exception if Exception === app_exception
57
53
  end
58
54
 
59
55
  private
60
56
 
57
+ def acceptable_content_type?(headers)
58
+ Mime::Type.parse(headers['Content-Type']).first == Mime::HTML
59
+ end
60
+
61
61
  def json_response(opts = {})
62
62
  status = opts.fetch(:status, 200)
63
63
  headers = { 'Content-Type' => 'application/json; charset = utf-8' }
@@ -67,17 +67,10 @@ module WebConsole
67
67
  end
68
68
 
69
69
  def json_response_with_session(id, request, opts = {})
70
- json_response(opts) do
71
- unless request.acceptable?
72
- return respond_with_unacceptable_request
73
- end
70
+ return respond_with_unacceptable_request unless request.acceptable?
71
+ return respond_with_unavailable_session(id) unless session = Session.find(id)
74
72
 
75
- unless session = Session.find(id)
76
- return respond_with_unavailable_session(id)
77
- end
78
-
79
- yield session
80
- end
73
+ json_response(opts) { yield session }
81
74
  end
82
75
 
83
76
  def create_regular_or_whiny_request(env)
@@ -85,23 +78,27 @@ module WebConsole
85
78
  whiny_requests ? WhinyRequest.new(request) : request
86
79
  end
87
80
 
81
+ def repl_sessions_re
82
+ @_repl_sessions_re ||= %r{#{mount_point}/repl_sessions/(?<id>[^/]+)}
83
+ end
84
+
88
85
  def update_re
89
- @options[:update_re]
86
+ @_update_re ||= %r{#{repl_sessions_re}\z}
90
87
  end
91
88
 
92
89
  def binding_change_re
93
- @options[:binding_change_re]
90
+ @_binding_change_re ||= %r{#{repl_sessions_re}/trace\z}
94
91
  end
95
92
 
96
93
  def id_for_repl_session_update(request)
97
94
  if request.xhr? && request.put?
98
- update_re.match(request.path_info) { |m| m[:id] }
95
+ update_re.match(request.path) { |m| m[:id] }
99
96
  end
100
97
  end
101
98
 
102
99
  def id_for_repl_session_stack_frame_change(request)
103
100
  if request.xhr? && request.post?
104
- binding_change_re.match(request.path_info) { |m| m[:id] }
101
+ binding_change_re.match(request.path) { |m| m[:id] }
105
102
  end
106
103
  end
107
104
 
@@ -121,14 +118,20 @@ module WebConsole
121
118
 
122
119
  def respond_with_unavailable_session(id)
123
120
  json_response(status: 404) do
124
- { output: format(UNAVAILABLE_SESSION_MESSAGE, id: id)}
121
+ { output: format(I18n.t('errors.unavailable_session'), id: id)}
125
122
  end
126
123
  end
127
124
 
128
125
  def respond_with_unacceptable_request
129
126
  json_response(status: 406) do
130
- { error: UNACCEPTABLE_REQUEST_MESSAGE }
127
+ { output: I18n.t('errors.unacceptable_request') }
131
128
  end
132
129
  end
130
+
131
+ def call_app(env)
132
+ @app.call(env)
133
+ rescue => e
134
+ throw :app_exception, e
135
+ end
133
136
  end
134
137
  end
@@ -5,10 +5,6 @@ module WebConsole
5
5
  config.web_console = ActiveSupport::OrderedOptions.new
6
6
  config.web_console.whitelisted_ips = %w( 127.0.0.1 ::1 )
7
7
 
8
- # See rails/web-console#150 and rails/rails#20319. Revert when Ruby on
9
- # Rails 4.2.4 is released.
10
- config.web_console.development_only = false
11
-
12
8
  initializer 'web_console.initialize' do
13
9
  require 'web_console/extensions'
14
10
 
@@ -43,6 +39,12 @@ module WebConsole
43
39
  app.middleware.insert_before ActionDispatch::DebugExceptions, Middleware
44
40
  end
45
41
 
42
+ initializer 'web_console.mount_point' do
43
+ if mount_point = config.web_console.mount_point
44
+ Middleware.mount_point = mount_point.chomp('/')
45
+ end
46
+ end
47
+
46
48
  initializer 'web_console.template_paths' do
47
49
  if template_paths = config.web_console.template_paths
48
50
  Template.template_paths.unshift(*Array(template_paths))
@@ -61,12 +63,8 @@ module WebConsole
61
63
  end
62
64
  end
63
65
 
64
- # Leave this undocumented so we treat such content type misses as bugs,
65
- # while still being able to help the affected users in the meantime.
66
- initializer 'web_console.acceptable_content_types' do
67
- if acceptable_content_types = config.web_console.acceptable_content_types
68
- Request.acceptable_content_types.concat(Array(acceptable_content_types))
69
- end
66
+ initializer 'i18n.load_path' do
67
+ config.i18n.load_path.concat(Dir[File.expand_path('../locales/*.yml', __FILE__)])
70
68
  end
71
69
  end
72
70
  end
@@ -1,11 +1,6 @@
1
1
  module WebConsole
2
2
  # Web Console tailored request object.
3
3
  class Request < ActionDispatch::Request
4
- # While most of the servers will return blank content type if none given,
5
- # Puma will return text/plain.
6
- cattr_accessor :acceptable_content_types
7
- @@acceptable_content_types = [Mime::HTML, Mime::TEXT, Mime::URL_ENCODED_FORM]
8
-
9
4
  # Configurable set of whitelisted networks.
10
5
  cattr_accessor :whitelisted_ips
11
6
  @@whitelisted_ips = Whitelist.new
@@ -23,16 +18,7 @@ module WebConsole
23
18
 
24
19
  # Determines the remote IP using our much stricter whitelist.
25
20
  def strict_remote_ip
26
- GetSecureIp.new(env, whitelisted_ips).to_s
27
- end
28
-
29
- # Returns whether the request is from an acceptable content type.
30
- #
31
- # We can render a console for HTML and TEXT by default. If a client didn't
32
- # specified any content type and the server returned it as blank, we'll
33
- # render it as well.
34
- def acceptable_content_type?
35
- content_type.blank? || content_type.in?(acceptable_content_types)
21
+ GetSecureIp.new(self, whitelisted_ips).to_s
36
22
  end
37
23
 
38
24
  # Returns whether the request is acceptable.
@@ -40,18 +26,25 @@ module WebConsole
40
26
  xhr? && accepts.any? { |mime| Mime::WEB_CONSOLE_V2 == mime }
41
27
  end
42
28
 
43
- class GetSecureIp < ActionDispatch::RemoteIp::GetIp
44
- def initialize(env, proxies)
45
- @env = env
46
- @check_ip = true
47
- @proxies = proxies
48
- end
29
+ private
30
+
31
+ class GetSecureIp < ActionDispatch::RemoteIp::GetIp
32
+ def initialize(req, proxies)
33
+ # After rails/rails@07b2ff0 ActionDispatch::RemoteIp::GetIp initializes
34
+ # with a ActionDispatch::Request object instead of plain Rack
35
+ # environment hash. Keep both @req and @env here, so we don't if/else
36
+ # on Rails versions.
37
+ @req = req
38
+ @env = req.env
39
+ @check_ip = true
40
+ @proxies = proxies
41
+ end
49
42
 
50
- def filter_proxies(ips)
51
- ips.reject do |ip|
52
- @proxies.include?(ip)
43
+ def filter_proxies(ips)
44
+ ips.reject do |ip|
45
+ @proxies.include?(ip)
46
+ end
53
47
  end
54
48
  end
55
- end
56
49
  end
57
50
  end
@@ -0,0 +1,23 @@
1
+ module WebConsole
2
+ # A response object that writes content before the closing </body> tag, if
3
+ # possible.
4
+ #
5
+ # The object quacks like Rack::Response.
6
+ class Response < Struct.new(:body, :status, :headers)
7
+ def write(content)
8
+ raw_body = Array(body).first.to_s
9
+
10
+ if position = raw_body.rindex('</body>')
11
+ raw_body.insert(position, content)
12
+ else
13
+ raw_body << content
14
+ end
15
+
16
+ self.body = raw_body
17
+ end
18
+
19
+ def finish
20
+ Rack::Response.new(body, status, headers).finish
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ namespace :ext do
2
+ rootdir = Pathname('extensions')
3
+
4
+ desc 'Build Chrome Extension'
5
+ task chrome: 'chrome:build'
6
+
7
+ namespace :chrome do
8
+ dist = Pathname('dist/crx')
9
+ extdir = rootdir.join(dist)
10
+ manifest_json = rootdir.join('chrome/manifest.json')
11
+
12
+ directory extdir
13
+
14
+ task build: [ extdir, 'lib:templates' ] do
15
+ cd rootdir do
16
+ cp_r [ 'img/', 'tmp/lib/' ], dist
17
+ `cd chrome && git ls-files`.split("\n").each do |src|
18
+ dest = dist.join(src)
19
+ mkdir_p dest.dirname
20
+ cp Pathname('chrome').join(src), dest
21
+ end
22
+ end
23
+ end
24
+
25
+ # Generate a .crx file.
26
+ task crx: [ :build, :npm ] do
27
+ out = "crx-web-console-#{JSON.parse(File.read(manifest_json))["version"]}.crx"
28
+ cd(extdir) { sh "node \"$(npm bin)/crx\" pack ./ -p ../crx-web-console.pem -o ../#{out}" }
29
+ end
30
+
31
+ # Generate a .zip file for Chrome Web Store.
32
+ task zip: [ :build ] do
33
+ version = JSON.parse(File.read(manifest_json))["version"]
34
+ cd(extdir) { sh "zip -r ../crx-web-console-#{version}.zip ./" }
35
+ end
36
+
37
+ desc 'Launch a browser with the chrome extension.'
38
+ task run: [ :build ] do
39
+ cd(rootdir) { sh "sh ./script/run_chrome.sh --load-extension=#{dist}" }
40
+ end
41
+ end
42
+
43
+ task :npm do
44
+ cd(rootdir) { sh "npm install --silent" }
45
+ end
46
+
47
+ namespace :lib do
48
+ templates = Pathname('lib/web_console/templates')
49
+ tmplib = rootdir.join('tmp/lib/')
50
+ js_erb = FileList.new(templates.join('**/*.js.erb'))
51
+ dirs = js_erb.pathmap("%{^#{templates},#{tmplib}}d")
52
+
53
+ task templates: dirs + js_erb.pathmap("%{^#{templates},#{tmplib}}X")
54
+
55
+ dirs.each { |d| directory d }
56
+ rule '.js' => [ "%{^#{tmplib},#{templates}}X.js.erb" ] do |t|
57
+ File.write(t.name, WebConsole::Testing::ERBPrecompiler.new(t.source).build)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ namespace :test do
2
+ desc "Run tests for templates"
3
+ task templates: "templates:all"
4
+
5
+ namespace :templates do
6
+ task all: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
7
+ task serve: [ :npm, :rackup ]
8
+
9
+ work_dir = Pathname(EXPANDED_CWD).join("test/templates")
10
+ pid_file = Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid")
11
+ runner_uri = URI.parse("http://localhost:29292/html/spec_runner.html")
12
+ rackup_opts = "-p #{runner_uri.port}"
13
+ test_result = nil
14
+
15
+ def need_to_wait?(uri)
16
+ Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
17
+ rescue Errno::ECONNREFUSED
18
+ retry if yield
19
+ end
20
+
21
+ task :daemonize do
22
+ rackup_opts += " -D -P #{pid_file}"
23
+ end
24
+
25
+ task :npm do
26
+ Dir.chdir(work_dir) { system "npm install --silent" }
27
+ end
28
+
29
+ task :rackup do
30
+ Dir.chdir(work_dir) { system "bundle exec rackup #{rackup_opts}" }
31
+ end
32
+
33
+ task :wait do
34
+ cnt = 0
35
+ need_to_wait?(runner_uri) { sleep 1; cnt += 1; cnt < 5 }
36
+ end
37
+
38
+ task :mocha do
39
+ Dir.chdir(work_dir) { test_result = system("$(npm bin)/mocha-phantomjs #{runner_uri}") }
40
+ end
41
+
42
+ task :kill do
43
+ system "kill #{File.read pid_file}"
44
+ end
45
+
46
+ task :exit do
47
+ exit test_result
48
+ end
49
+ end
50
+ end
@@ -4,33 +4,6 @@ module WebConsole
4
4
  # It introduces template helpers to ease the inclusion of scripts only on
5
5
  # Rails error pages.
6
6
  class Template
7
- class Context < ActionView::Base
8
- # Execute a block only on error pages.
9
- #
10
- # The error pages are special, because they are the only pages that
11
- # currently require multiple bindings. We get those from exceptions.
12
- def only_on_error_page(*args)
13
- yield if @env['web_console.exception'].present?
14
- end
15
-
16
- # Render JavaScript inside a script tag and a closure.
17
- #
18
- # This one lets write JavaScript that will automatically get wrapped in a
19
- # script tag and enclosed in a closure, so you don't have to worry for
20
- # leaking globals, unless you explicitly want to.
21
- def render_javascript(template)
22
- render(template: template, layout: 'layouts/javascript')
23
- end
24
-
25
- # Render inlined string to be used inside of JavaScript code.
26
- #
27
- # The inlined string is returned as an actual JavaScript string. You
28
- # don't need to wrap the result yourself.
29
- def render_inlined_string(template)
30
- render(template: template, layout: 'layouts/inlined_string')
31
- end
32
- end
33
-
34
7
  # Lets you customize the default templates folder location.
35
8
  cattr_accessor :template_paths
36
9
  @@template_paths = [ File.expand_path('../templates', __FILE__) ]
@@ -38,12 +11,13 @@ module WebConsole
38
11
  def initialize(env, session)
39
12
  @env = env
40
13
  @session = session
14
+ @mount_point = Middleware.mount_point
41
15
  end
42
16
 
43
17
  # Render a template (inferred from +template_paths+) as a plain string.
44
18
  def render(template)
45
- context = Context.new(template_paths, instance_values)
46
- context.render(template: template, layout: false)
19
+ view = View.new(template_paths, instance_values)
20
+ view.render(template: template, layout: false)
47
21
  end
48
22
  end
49
23
  end
@@ -1,4 +1,5 @@
1
1
  <div id="console"
2
- data-remote-path='<%= "console/repl_sessions/#{@session.id}" %>'
3
- data-initial-prompt='>> '>
2
+ data-mount-point='<%= @mount_point %>'
3
+ data-session-id='<%= @session.id %>'
4
+ data-prompt-label='>> '>
4
5
  </div>
@@ -54,11 +54,69 @@ var styleElementId = 'sr02459pvbvrmhco';
54
54
 
55
55
  // REPLConsole Constructor
56
56
  function REPLConsole(config) {
57
+ function getConfig(key, defaultValue) {
58
+ return config && config[key] || defaultValue;
59
+ }
60
+
57
61
  this.commandStorage = new CommandStorage();
58
- this.prompt = config && config.promptLabel ? config.promptLabel : ' >>';
59
- this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; }
62
+ this.prompt = getConfig('promptLabel', ' >>');
63
+ this.mountPoint = getConfig('mountPoint');
64
+ this.sessionId = getConfig('sessionId');
60
65
  }
61
66
 
67
+ REPLConsole.prototype.getSessionUrl = function(path) {
68
+ var parts = [ this.mountPoint, 'repl_sessions', this.sessionId ];
69
+ if (path) {
70
+ parts.push(path);
71
+ }
72
+ // Join and remove duplicate slashes.
73
+ return parts.join('/').replace(/([^:]\/)\/+/g, '$1');
74
+ };
75
+
76
+ REPLConsole.prototype.commandHandle = function(line, callback) {
77
+ var self = this;
78
+ var params = 'input=' + encodeURIComponent(line);
79
+ callback = callback || function() {};
80
+
81
+ function isSuccess(status) {
82
+ return status >= 200 && status < 300 || status === 304;
83
+ }
84
+
85
+ function parseJSON(text) {
86
+ try {
87
+ return JSON.parse(text);
88
+ } catch (e) {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ function getErrorText(xhr) {
94
+ if (!xhr.status) {
95
+ return "<%= t 'errors.connection_refused' %>";
96
+ } else {
97
+ return xhr.status + ' ' + xhr.statusText;
98
+ }
99
+ }
100
+
101
+ putRequest(self.getSessionUrl(), params, function(xhr) {
102
+ var response = parseJSON(xhr.responseText);
103
+ var result = isSuccess(xhr.status);
104
+ if (result) {
105
+ self.writeOutput(response.output);
106
+ } else {
107
+ if (response && response.output) {
108
+ self.writeError(response.output);
109
+ } else {
110
+ self.writeError(getErrorText(xhr));
111
+ }
112
+ }
113
+ callback(result, response);
114
+ });
115
+ };
116
+
117
+ REPLConsole.prototype.uninstall = function() {
118
+ this.container.parentNode.removeChild(this.container);
119
+ };
62
120
 
63
121
  REPLConsole.prototype.install = function(container) {
64
122
  var _this = this;
@@ -99,8 +157,8 @@ REPLConsole.prototype.install = function(container) {
99
157
 
100
158
  // Make the console resizable.
101
159
  function resizeContainer(ev) {
102
- var startY = ev.clientY;
103
- var startHeight = parseInt(document.defaultView.getComputedStyle(container).height, 10);
160
+ var startY = ev.clientY;
161
+ var startHeight = parseInt(document.defaultView.getComputedStyle(container).height, 10);
104
162
  var scrollTopStart = consoleOuter.scrollTop;
105
163
  var clientHeightStart = consoleOuter.clientHeight;
106
164
 
@@ -137,10 +195,10 @@ REPLConsole.prototype.install = function(container) {
137
195
  }
138
196
 
139
197
  // Initialize
198
+ this.container = container;
140
199
  this.outer = consoleOuter;
141
200
  this.inner = findChild(this.outer, 'console-inner');
142
201
  this.clipboard = findChild(container, 'clipboard');
143
- this.remotePath = container.dataset.remotePath;
144
202
  this.newPromptBox();
145
203
  this.insertCss();
146
204
 
@@ -263,6 +321,13 @@ REPLConsole.prototype.writeOutput = function(output) {
263
321
  consoleMessage.innerHTML = escapeHTML(output);
264
322
  this.inner.appendChild(consoleMessage);
265
323
  this.newPromptBox();
324
+ return consoleMessage;
325
+ };
326
+
327
+ REPLConsole.prototype.writeError = function(output) {
328
+ var consoleMessage = this.writeOutput(output);
329
+ addClass(consoleMessage, "error-message");
330
+ return consoleMessage;
266
331
  };
267
332
 
268
333
  REPLConsole.prototype.onEnterKey = function() {
@@ -385,7 +450,7 @@ REPLConsole.prototype.scrollToBottom = function() {
385
450
 
386
451
  // Change the binding of the console
387
452
  REPLConsole.prototype.switchBindingTo = function(frameId, callback) {
388
- var url = this.remotePath + "/trace";
453
+ var url = this.getSessionUrl('trace');
389
454
  var params = "frame_id=" + encodeURIComponent(frameId);
390
455
  postRequest(url, params, callback);
391
456
  };
@@ -394,22 +459,16 @@ REPLConsole.prototype.switchBindingTo = function(frameId, callback) {
394
459
  * Install the console into the element with a specific ID.
395
460
  * Example: REPLConsole.installInto("target-id")
396
461
  */
397
- REPLConsole.installInto = function(id) {
462
+ REPLConsole.installInto = function(id, options) {
398
463
  var consoleElement = document.getElementById(id);
399
- var remotePath = consoleElement.dataset.remotePath;
400
- var replConsole = new REPLConsole({
401
- promptLabel: consoleElement.dataset.initialPrompt,
402
- commandHandle: function(line) {
403
- var _this = this;
404
- var url = remotePath;
405
- var params = "input=" + encodeURIComponent(line);
406
- putRequest(url, params, function(xhr) {
407
- var response = JSON.parse(xhr.responseText);
408
- _this.writeOutput(response.output);
409
- });
410
- }
411
- });
412
464
 
465
+ options = options || {};
466
+
467
+ for (var prop in consoleElement.dataset) {
468
+ options[prop] = options[prop] || consoleElement.dataset[prop];
469
+ }
470
+
471
+ var replConsole = new REPLConsole(options);
413
472
  replConsole.install(consoleElement);
414
473
  return replConsole;
415
474
  };
@@ -419,6 +478,26 @@ REPLConsole.installInto = function(id) {
419
478
  // It allows to operate the current session from the other scripts.
420
479
  REPLConsole.currentSession = null;
421
480
 
481
+ // This line is for the Firefox Add-on, because it doesn't have XMLHttpRequest as default.
482
+ // And so we need to require a module compatible with XMLHttpRequest from SDK.
483
+ REPLConsole.XMLHttpRequest = typeof XMLHttpRequest === 'undefined' ? null : XMLHttpRequest;
484
+
485
+ REPLConsole.request = function request(method, url, params, callback) {
486
+ var xhr = new REPLConsole.XMLHttpRequest();
487
+
488
+ xhr.open(method, url, true);
489
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
490
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
491
+ xhr.setRequestHeader("Accept", "<%= Mime::WEB_CONSOLE_V2 %>");
492
+ xhr.send(params);
493
+
494
+ xhr.onreadystatechange = function() {
495
+ if (xhr.readyState === 4) {
496
+ callback(xhr);
497
+ }
498
+ };
499
+ };
500
+
422
501
  // DOM helpers
423
502
  function hasClass(el, className) {
424
503
  var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
@@ -470,28 +549,16 @@ function escapeHTML(html) {
470
549
  }
471
550
 
472
551
  // XHR helpers
473
- function request(method, url, params, callback) {
474
- var xhr = new XMLHttpRequest();
475
-
476
- xhr.open(method, url, true);
477
- xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
478
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
479
- xhr.setRequestHeader("Accept", "<%= Mime::WEB_CONSOLE_V2 %>");
480
- xhr.send(params);
481
-
482
- xhr.onreadystatechange = function() {
483
- if (xhr.readyState === 4) {
484
- callback(xhr);
485
- }
486
- }
552
+ function postRequest() {
553
+ REPLConsole.request.apply(this, ["POST"].concat([].slice.call(arguments)));
487
554
  }
488
555
 
489
- function postRequest(url, params, callback) {
490
- request("POST", url, params, callback);
556
+ function putRequest() {
557
+ REPLConsole.request.apply(this, ["PUT"].concat([].slice.call(arguments)));
491
558
  }
492
559
 
493
- function putRequest(url, params, callback) {
494
- request("PUT", url, params, callback);
560
+ if (typeof exports === 'object') {
561
+ exports.REPLConsole = REPLConsole;
562
+ } else {
563
+ window.REPLConsole = REPLConsole;
495
564
  }
496
-
497
- window.REPLConsole = REPLConsole;
@@ -10,6 +10,7 @@
10
10
  .console .console-inner { font-family: monospace; font-size: 11px; width: 100%; height: 100%; overflow: none; background: #333; }
11
11
  .console .console-prompt-box { color: #FFF; }
12
12
  .console .console-message { color: #1AD027; margin: 0; border: 0; white-space: pre-wrap; background-color: #333; padding: 0; }
13
+ .console .console-message.error-message { color: #fc9; }
13
14
  .console .console-focus .console-cursor { background: #FEFEFE; color: #333; font-weight: bold; }
14
15
  .console .resizer { background: #333; width: 100%; height: 4px; cursor: ns-resize; }
15
16
  .console .console-actions { padding-right: 3px; }
@@ -20,3 +21,7 @@
20
21
  .console .clipboard { height: 0px; padding: 0px; margin: 0px; width: 0px; margin-left: -1000px; }
21
22
  .console .console-prompt-label { display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0; }
22
23
  .console .console-prompt-display { display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0; }
24
+ .console.full-screen { height: 100%; }
25
+ .console.full-screen .console-outer { padding-top: 3px; }
26
+ .console.full-screen .resizer { display: none; }
27
+ .console.full-screen .close-button { display: none; }
@@ -0,0 +1,25 @@
1
+ require 'web_console/testing/helper'
2
+ require 'web_console/testing/fake_middleware'
3
+
4
+ module WebConsole
5
+ module Testing
6
+ # This class is to pre-compile 'templates/*.erb'.
7
+ class ERBPrecompiler
8
+ def initialize(path)
9
+ @erb = ERB.new(File.read(path))
10
+ @view = FakeMiddleware.new(
11
+ view_path: Helper.gem_root.join('lib/web_console/templates'),
12
+ ).view
13
+ end
14
+
15
+ def build
16
+ @erb.result(binding)
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ return super unless @view.respond_to?(name)
21
+ @view.send(name, *args, &block)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'action_view'
2
+ require 'action_dispatch'
3
+ require 'active_support/core_ext/string/access'
4
+ require 'json'
5
+ require 'web_console/whitelist'
6
+ require 'web_console/request'
7
+ require 'web_console/view'
8
+ require 'web_console/testing/helper'
9
+
10
+ module WebConsole
11
+ module Testing
12
+ class FakeMiddleware
13
+ I18n.load_path.concat(Dir[Helper.gem_root.join('lib/web_console/locales/*.yml')])
14
+
15
+ DEFAULT_HEADERS = { "Content-Type" => "application/javascript" }
16
+
17
+ def initialize(opts)
18
+ @headers = opts.fetch(:headers, DEFAULT_HEADERS)
19
+ @req_path_regex = opts[:req_path_regex]
20
+ @view_path = opts[:view_path]
21
+ end
22
+
23
+ def call(env)
24
+ [ 200, @headers, [ render(req_path(env)) ] ]
25
+ end
26
+
27
+ def view
28
+ @view ||= View.new(@view_path)
29
+ end
30
+
31
+ private
32
+
33
+ # extract target path from REQUEST_PATH
34
+ def req_path(env)
35
+ env["REQUEST_PATH"].match(@req_path_regex)[1]
36
+ end
37
+
38
+ def render(template)
39
+ view.render(template: template, layout: nil)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ module WebConsole
2
+ module Testing
3
+ module Helper
4
+ def self.gem_root
5
+ Pathname(File.expand_path('../../../../', __FILE__))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module WebConsole
2
- VERSION = '2.2.1'
2
+ VERSION = '2.3.0'
3
3
  end
@@ -0,0 +1,37 @@
1
+ module WebConsole
2
+ class View < ActionView::Base
3
+ # Execute a block only on error pages.
4
+ #
5
+ # The error pages are special, because they are the only pages that
6
+ # currently require multiple bindings. We get those from exceptions.
7
+ def only_on_error_page(*args)
8
+ yield if @env['web_console.exception'].present?
9
+ end
10
+
11
+ # Render JavaScript inside a script tag and a closure.
12
+ #
13
+ # This one lets write JavaScript that will automatically get wrapped in a
14
+ # script tag and enclosed in a closure, so you don't have to worry for
15
+ # leaking globals, unless you explicitly want to.
16
+ def render_javascript(template)
17
+ render(template: template, layout: 'layouts/javascript')
18
+ end
19
+
20
+ # Render inlined string to be used inside of JavaScript code.
21
+ #
22
+ # The inlined string is returned as an actual JavaScript string. You
23
+ # don't need to wrap the result yourself.
24
+ def render_inlined_string(template)
25
+ render(template: template, layout: 'layouts/inlined_string')
26
+ end
27
+
28
+ # Override method for ActionView::Helpers::TranslationHelper#t.
29
+ #
30
+ # This method escapes the original return value for JavaScript, since the
31
+ # method returns a HTML tag with some attributes when the key is not found,
32
+ # so it could cause a syntax error if we use the value in the string literals.
33
+ def t(key, options = {})
34
+ j super
35
+ end
36
+ end
37
+ end
@@ -11,13 +11,6 @@ module WebConsole
11
11
  end
12
12
  end
13
13
 
14
- def acceptable_content_type?
15
- whine_unless request.acceptable_content_type? do
16
- "Cannot render console with content type #{request.content_type}" \
17
- "Allowed content types: #{request.acceptable_content_types}"
18
- end
19
- end
20
-
21
14
  private
22
15
 
23
16
  def whine_unless(condition)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web-console
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Somerville
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-07-10 00:00:00.000000000 Z
14
+ date: 2016-01-27 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -127,10 +127,14 @@ files:
127
127
  - lib/web_console/integration/cruby.rb
128
128
  - lib/web_console/integration/jruby.rb
129
129
  - lib/web_console/integration/rubinius.rb
130
+ - lib/web_console/locales/en.yml
130
131
  - lib/web_console/middleware.rb
131
132
  - lib/web_console/railtie.rb
132
133
  - lib/web_console/request.rb
134
+ - lib/web_console/response.rb
133
135
  - lib/web_console/session.rb
136
+ - lib/web_console/tasks/extensions.rake
137
+ - lib/web_console/tasks/test_templates.rake
134
138
  - lib/web_console/template.rb
135
139
  - lib/web_console/templates/_inner_console_markup.html.erb
136
140
  - lib/web_console/templates/_markup.html.erb
@@ -142,8 +146,11 @@ files:
142
146
  - lib/web_console/templates/layouts/javascript.erb
143
147
  - lib/web_console/templates/main.js.erb
144
148
  - lib/web_console/templates/style.css.erb
145
- - lib/web_console/tracer.rb
149
+ - lib/web_console/testing/erb_precompiler.rb
150
+ - lib/web_console/testing/fake_middleware.rb
151
+ - lib/web_console/testing/helper.rb
146
152
  - lib/web_console/version.rb
153
+ - lib/web_console/view.rb
147
154
  - lib/web_console/whiny_request.rb
148
155
  - lib/web_console/whitelist.rb
149
156
  homepage: https://github.com/rails/web-console
@@ -166,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
173
  version: '0'
167
174
  requirements: []
168
175
  rubyforge_project:
169
- rubygems_version: 2.4.5
176
+ rubygems_version: 2.5.1
170
177
  signing_key:
171
178
  specification_version: 4
172
179
  summary: A debugging tool for your Ruby on Rails applications.
@@ -1,11 +0,0 @@
1
- module WebConsole
2
- class BindingTracer
3
- def initialize(exception)
4
- @bindings = exception.bindings
5
- @backtrace = exception.backtrace
6
- end
7
-
8
- def binding_for_trace(trace)
9
- end
10
- end
11
- end