rvt 0.9.9

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +25 -0
  4. data/app/assets/javascripts/rvt/application.js +1 -0
  5. data/app/assets/javascripts/rvt/console_sessions.js +182 -0
  6. data/app/assets/stylesheets/rvt/application.css +13 -0
  7. data/app/assets/stylesheets/rvt/console_sessions.css.erb +6 -0
  8. data/app/controllers/rvt/application_controller.rb +13 -0
  9. data/app/controllers/rvt/console_sessions_controller.rb +47 -0
  10. data/app/models/rvt/console_session.rb +109 -0
  11. data/app/views/layouts/rvt/application.html.erb +14 -0
  12. data/app/views/rvt/console_sessions/index.html.erb +17 -0
  13. data/config/routes.rb +11 -0
  14. data/lib/assets/javascripts/rvt.js +41 -0
  15. data/lib/rvt.rb +21 -0
  16. data/lib/rvt/colors.rb +87 -0
  17. data/lib/rvt/colors/light.rb +24 -0
  18. data/lib/rvt/colors/monokai.rb +24 -0
  19. data/lib/rvt/colors/solarized.rb +47 -0
  20. data/lib/rvt/colors/tango.rb +24 -0
  21. data/lib/rvt/colors/xterm.rb +24 -0
  22. data/lib/rvt/engine.rb +85 -0
  23. data/lib/rvt/slave.rb +147 -0
  24. data/lib/rvt/version.rb +3 -0
  25. data/test/controllers/rvt/console_sessions_controller_test.rb +100 -0
  26. data/test/dummy/Rakefile +6 -0
  27. data/test/dummy/app/assets/javascripts/application.js +13 -0
  28. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  29. data/test/dummy/app/controllers/application_controller.rb +5 -0
  30. data/test/dummy/app/helpers/application_helper.rb +2 -0
  31. data/test/dummy/app/views/controller_helper_test/index.html.erb +1 -0
  32. data/test/dummy/app/views/exception_test/xhr.html.erb +1 -0
  33. data/test/dummy/app/views/helper_test/index.html.erb +220 -0
  34. data/test/dummy/app/views/layouts/application.html.erb +16 -0
  35. data/test/dummy/bin/bundle +3 -0
  36. data/test/dummy/bin/rails +4 -0
  37. data/test/dummy/bin/rake +4 -0
  38. data/test/dummy/config.ru +4 -0
  39. data/test/dummy/config/application.rb +44 -0
  40. data/test/dummy/config/boot.rb +5 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/development.rb +29 -0
  44. data/test/dummy/config/environments/production.rb +80 -0
  45. data/test/dummy/config/environments/test.rb +34 -0
  46. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  47. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/test/dummy/config/initializers/inflections.rb +16 -0
  49. data/test/dummy/config/initializers/mime_types.rb +5 -0
  50. data/test/dummy/config/initializers/secret_token.rb +12 -0
  51. data/test/dummy/config/initializers/session_store.rb +3 -0
  52. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  53. data/test/dummy/config/locales/en.yml +23 -0
  54. data/test/dummy/config/routes.rb +2 -0
  55. data/test/dummy/db/development.sqlite3 +0 -0
  56. data/test/dummy/db/schema.rb +16 -0
  57. data/test/dummy/db/test.sqlite3 +0 -0
  58. data/test/dummy/log/development.log +247222 -0
  59. data/test/dummy/log/test.log +963 -0
  60. data/test/dummy/public/404.html +58 -0
  61. data/test/dummy/public/422.html +58 -0
  62. data/test/dummy/public/500.html +57 -0
  63. data/test/dummy/public/favicon.ico +0 -0
  64. data/test/dummy/tmp/cache/assets/development/sprockets/0509a6e0e75d9ac5a88bba7291b04686 +0 -0
  65. data/test/dummy/tmp/cache/assets/development/sprockets/2bd9d10dae311aa2dfb6dea9c1e0ad50 +0 -0
  66. data/test/dummy/tmp/cache/assets/development/sprockets/2f41a6a41d0a16db31cabd2b8689ca00 +0 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/4de66e84a0d6f009fac50cd608fb8581 +0 -0
  68. data/test/dummy/tmp/cache/assets/development/sprockets/56e8026311075410507152df2aea4307 +0 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/9542de15712d45f70221931bf78c00e5 +0 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/98cea396a602c53d876e79bf4b89de3b +0 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/9df3968b0f171feec749766fffa50b2e +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/aa5fc4cb46c5294192c9d3af8824f88b +0 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/ac7197dc7dd9ab362d915d19e37a8e01 +0 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/b238befd6eff46b26d56322c1450ea45 +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/c69b8b79c21fd48ad84c3fad87945a5c +0 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/da4318a4d8364d0616edd706508371bc +0 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/dd71b14df42fd6dc95355cded9e4d0f9 +0 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/eb06cae1627276e46965809e766aff13 +0 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/edffb5017d27ddb65965203636286405 +0 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/f0ced5f3d4a75fce1c596d6c3f97a42d +0 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/fb9e611526e612ba797f0c11b987826b +0 -0
  82. data/test/models/console_session_test.rb +58 -0
  83. data/test/rvt/colors_test.rb +58 -0
  84. data/test/rvt/engine_test.rb +145 -0
  85. data/test/rvt/slave_test.rb +72 -0
  86. data/test/test_helper.rb +27 -0
  87. data/vendor/assets/javascripts/term.js +5771 -0
  88. metadata +284 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40c0eb5241121c94ef06e819351e3622dec19f87
4
+ data.tar.gz: 33f5a3b57e0f16bddda6cf8568d0a126403fd9e9
5
+ SHA512:
6
+ metadata.gz: 473f3b32b24b91a76f45a46b19f557b0f96a3bb8f5ec3040bf2d1a09f265d662d3952875a8aec3cf45e045bb3683c7e6c8d9229382cd063399627c0ab5033344
7
+ data.tar.gz: 8decfdb59c9293d84bac29d98fe546d3da4ef8e944d76c688e158e3284db8a6289933113c3aaed683f02c94c3d7e1451b720e9888e30c1a6711ecde5e7b9f85d
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Genadi Samokovarov, Guillermo Iguaran
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'socket'
8
+ require 'active_support/core_ext/string/strip'
9
+ require 'rake/testtask'
10
+
11
+ EXPANDED_CWD = File.expand_path(File.dirname(__FILE__))
12
+
13
+ Rake::TestTask.new(:test) do |t|
14
+ t.libs << 'lib'
15
+ t.libs << 'test'
16
+ t.pattern = 'test/**/*_test.rb'
17
+ t.verbose = false
18
+ end
19
+
20
+ # Just ignore this if rake is not runned from the current directory, as is the
21
+ # case with docker's container. BUNDLE_GEMFILE won't do for our case, since the
22
+ # Gemfile references gemspec.
23
+ Bundler::GemHelper.install_tasks if defined? Bundler::GemHelper
24
+
25
+ task default: :test
@@ -0,0 +1 @@
1
+ //= require_tree .
@@ -0,0 +1,182 @@
1
+ //= require rvt
2
+
3
+ var AJAXTransport = (function(RVT) {
4
+
5
+ var inherits = RVT.inherits;
6
+ var EventEmitter = RVT.EventEmitter;
7
+
8
+ var FORM_MIME_TYPE = 'application/x-www-form-urlencoded; charset=utf-8';
9
+
10
+ var AJAXTransport = function(options) {
11
+ EventEmitter.call(this);
12
+ options || (options = {});
13
+
14
+ this.url = (typeof options.url === 'string') ? {
15
+ input: options.url,
16
+ pendingOutput: options.url,
17
+ configuration: options.url
18
+ } : options.url;
19
+
20
+ this.uid = options.uid;
21
+ this.pendingInput = '';
22
+
23
+ this.initializeEventHandlers();
24
+ };
25
+
26
+ inherits(AJAXTransport, EventEmitter);
27
+
28
+ // Initializes the default event handlers.
29
+ AJAXTransport.prototype.initializeEventHandlers = function() {
30
+ this.on('input', this.sendInput);
31
+ this.on('configuration', this.sendConfiguration);
32
+ this.once('initialization', function(cols, rows) {
33
+ this.emit('configuration', cols, rows);
34
+ this.pollForPendingOutput();
35
+ });
36
+ };
37
+
38
+ // Shorthand for creating XHR requests.
39
+ AJAXTransport.prototype.createRequest = function(method, url, options) {
40
+ options || (options = {});
41
+
42
+ var params = '';
43
+
44
+ if (typeof options.form === 'object') {
45
+ var content = [], form = options.form;
46
+
47
+ for (var key in form) {
48
+ var value = form[key];
49
+ content.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
50
+ }
51
+
52
+ params = content.join('&');
53
+ if (method === 'GET' ? '?' : '') params = '?' + params;
54
+ }
55
+
56
+ var request = new XMLHttpRequest;
57
+ request.open(method, method === 'GET' ? url + params : url);
58
+
59
+ if (params && method != 'GET') {
60
+ request.setRequestHeader('Content-Type', FORM_MIME_TYPE);
61
+ request.data = params;
62
+ }
63
+
64
+ return request;
65
+ };
66
+
67
+ AJAXTransport.prototype.pollForPendingOutput = function() {
68
+ var request = this.createRequest('GET', this.url.pendingOutput, {
69
+ form: { uid: this.uid }
70
+ });
71
+
72
+ var self = this;
73
+ request.onreadystatechange = function() {
74
+ if (request.readyState === XMLHttpRequest.DONE) {
75
+ if (request.status === 200) {
76
+ self.emit('pendingOutput', request.responseText);
77
+ self.pollForPendingOutput();
78
+ } else {
79
+ self.emit('disconnect', request);
80
+ }
81
+ }
82
+ };
83
+
84
+ request.send(null);
85
+ };
86
+
87
+ // Send the input to the server.
88
+ //
89
+ // Each key press is encoded to an intermediate format, before it is sent to
90
+ // the server.
91
+ //
92
+ // RVT#keysPressed is an alias for RVT#sendInput.
93
+ AJAXTransport.prototype.sendInput = function(input) {
94
+ input || (input = '');
95
+
96
+ if (this.disconnected) return;
97
+ if (this.sendingInput) return this.pendingInput += input;
98
+
99
+ // Indicate that we are starting to send input.
100
+ this.sendingInput = true;
101
+
102
+ var request = this.createRequest('PUT', this.url.input, {
103
+ form: { input: this.pendingInput + input, uid: this.uid }
104
+ });
105
+
106
+ // Clear the pending input.
107
+ this.pendingInput = '';
108
+
109
+ var self = this;
110
+ request.onreadystatechange = function() {
111
+ if (request.readyState === XMLHttpRequest.DONE) {
112
+ self.sendingInput = false;
113
+ if (self.pendingInput) self.sendInput();
114
+ }
115
+ };
116
+
117
+ request.send(request.data);
118
+ };
119
+
120
+ // Send the terminal configuration to the server.
121
+ //
122
+ // Right now by configuration, we understand the terminal widht and terminal
123
+ // height.
124
+ //
125
+ // RVT#resized is an alias for RVT#sendconfiguration.
126
+ AJAXTransport.prototype.sendConfiguration = function(cols, rows) {
127
+ if (this.disconnected) return;
128
+
129
+ var request = this.createRequest('PUT', this.url.configuration, {
130
+ form: { width: cols, height: rows, uid: this.uid }
131
+ });
132
+
133
+ // Just send the configuration and don't care about any output.
134
+ request.send(request.data);
135
+ };
136
+
137
+ return AJAXTransport;
138
+
139
+ }).call(this, RVT);
140
+
141
+ window.addEventListener('load', function() {
142
+ var geometry = calculateFitScreenGeometry();
143
+ config.terminal.cols = geometry[0];
144
+ config.terminal.rows = geometry[1];
145
+
146
+ var terminal = window.terminal = new RVT.Terminal(config.terminal);
147
+
148
+ terminal.on('data', function(data) {
149
+ transport.emit('input', data);
150
+ });
151
+
152
+ var transport = new AJAXTransport(config.transport);
153
+
154
+ transport.on('pendingOutput', function(response) {
155
+ var json = JSON.parse(response);
156
+ if (json.output) terminal.write(json.output);
157
+ });
158
+
159
+ transport.on('disconnect', function() {
160
+ terminal.destroy();
161
+ });
162
+
163
+ transport.emit('initialization', terminal.cols, terminal.rows);
164
+
165
+ // Utilities
166
+ // ---------
167
+
168
+ function calculateFitScreenGeometry() {
169
+ // Currently, resizing term.js is broken. E.g. opening vi causes it to go
170
+ // back to 80x24 and fail with off-by-one error. Other stuff, like chip8
171
+ // are rendered incorrectly and so on.
172
+ //
173
+ // To work around it, create a temporary terminal, just so we can get the
174
+ // best dimensions for the screen.
175
+ var temporary = new RVT.Terminal;
176
+ try {
177
+ return temporary.fitScreen();
178
+ } finally {
179
+ temporary.destroy();
180
+ }
181
+ }
182
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,6 @@
1
+ <% RVT.config.style.instance_eval do %>
2
+ body { color: <%= colors.foreground %>; background: <%= colors.background %>; margin: 0; padding: 0; }
3
+
4
+ .terminal { float: left; overflow: hidden; font: <%= font %>; }
5
+ .terminal-cursor { color: <%= colors.background %>; background: <%= colors.foreground %>; }
6
+ <% end %>
@@ -0,0 +1,13 @@
1
+ module RVT
2
+ class ApplicationController < ActionController::Base
3
+ before_action :prevent_unauthorized_requests!
4
+
5
+ private
6
+
7
+ def prevent_unauthorized_requests!
8
+ unless request.remote_ip.in?(RVT.config.whitelisted_ips)
9
+ render nothing: true, status: :unauthorized
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require_dependency 'rvt/application_controller'
2
+
3
+ module RVT
4
+ class ConsoleSessionsController < ApplicationController
5
+ rescue_from ConsoleSession::Unavailable do |exception|
6
+ render json: exception, status: :gone
7
+ end
8
+
9
+ rescue_from ConsoleSession::Invalid do |exception|
10
+ render json: exception, status: :unprocessable_entity
11
+ end
12
+
13
+ rescue_from ConsoleSession::Unauthorized do |exception|
14
+ render json: exception, status: :unauthorized
15
+ end
16
+
17
+ def index
18
+ @console_session = ConsoleSession.create
19
+ end
20
+
21
+ def input
22
+ @console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
23
+ @console_session.send_input(console_session_params[:input])
24
+
25
+ render nothing: true
26
+ end
27
+
28
+ def configuration
29
+ @console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
30
+ @console_session.configure(console_session_params)
31
+
32
+ render nothing: true
33
+ end
34
+
35
+ def pending_output
36
+ @console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
37
+
38
+ render json: { output: @console_session.pending_output }
39
+ end
40
+
41
+ private
42
+
43
+ def console_session_params
44
+ params.permit(:id, :uid, :input, :width, :height)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,109 @@
1
+ module RVT
2
+ # Manage and persist (in memory) RVT::Slave instances.
3
+ class ConsoleSession
4
+ include ActiveModel::Model
5
+
6
+ # In-memory storage for the console sessions. Session preservation is
7
+ # troubled on servers with multiple workers and threads.
8
+ INMEMORY_STORAGE = {}
9
+
10
+ # Base error class for ConsoleSession specific exceptions.
11
+ #
12
+ # Provides #to_json implementation, so all subclasses are JSON
13
+ # serializable.
14
+ class Error < StandardError
15
+ def as_json(*)
16
+ { error: to_s }
17
+ end
18
+ end
19
+
20
+ # Raised when trying to find a session that is no longer in the in-memory
21
+ # session storage or when the slave process exited.
22
+ Unavailable = Class.new(Error)
23
+
24
+ # Raised when an operation transition to an invalid state.
25
+ Invalid = Class.new(Error)
26
+
27
+ # Raised when a request doesn't know the slave process uid.
28
+ Unauthorized = Class.new(Error)
29
+
30
+ class << self
31
+ # Finds a session by its pid.
32
+ #
33
+ # Raises RVT::ConsoleSession::Expired if there is no such session.
34
+ def find(pid)
35
+ INMEMORY_STORAGE[pid.to_i] or raise Unavailable, 'Session unavailable'
36
+ end
37
+
38
+ # Finds a session by its pid.
39
+ #
40
+ # Raises RVT::ConsoleSession::Expired if there is no such session.
41
+ # Raises RVT::ConsoleSession::Unauthorized if uid doesn't match.
42
+ def find_by_pid_and_uid(pid, uid)
43
+ find(pid).tap do |console_session|
44
+ raise Unauthorized if console_session.uid != uid
45
+ end
46
+ end
47
+
48
+ # Creates an already persisted consolse session.
49
+ #
50
+ # Use this method if you need to persist a session, without providing it
51
+ # any input.
52
+ def create
53
+ new.persist
54
+ end
55
+ end
56
+
57
+ def initialize
58
+ @slave = Slave.new
59
+ end
60
+
61
+ # Explicitly persist the model in the in-memory storage.
62
+ def persist
63
+ INMEMORY_STORAGE[pid] = self
64
+ end
65
+
66
+ # Returns true if the current session is persisted in the in-memory storage.
67
+ def persisted?
68
+ self == INMEMORY_STORAGE[pid]
69
+ end
70
+
71
+ # Returns an Enumerable of all key attributes if any is set, regardless if
72
+ # the object is persisted or not.
73
+ def to_key
74
+ [pid] if persisted?
75
+ end
76
+
77
+ private
78
+
79
+ def delegate_and_call_slave_method(name, *args, &block)
80
+ # Cache the delegated method, so we don't have to hit #method_missing
81
+ # on every call.
82
+ define_singleton_method(name) do |*inner_args, &inner_block|
83
+ begin
84
+ @slave.public_send(name, *inner_args, &inner_block)
85
+ rescue ArgumentError => exc
86
+ raise Invalid, exc
87
+ rescue Slave::Closed => exc
88
+ raise Unavailable, exc
89
+ end
90
+ end
91
+
92
+ # Now call the method, since that's our most common use case. Delegate
93
+ # the method and than call it.
94
+ public_send(name, *args, &block)
95
+ end
96
+
97
+ def method_missing(name, *args, &block)
98
+ if @slave.respond_to?(name)
99
+ delegate_and_call_slave_method(name, *args, &block)
100
+ else
101
+ super
102
+ end
103
+ end
104
+
105
+ def respond_to_missing?(name, include_all = false)
106
+ @slave.respond_to?(name) or super
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RVT</title>
5
+
6
+ <%= stylesheet_link_tag 'rvt/application', media: 'all' %>
7
+ <%= javascript_include_tag 'rvt/application' %>
8
+ <%= csrf_meta_tags %>
9
+ </head>
10
+
11
+ <body>
12
+ <%= yield %>
13
+ </body>
14
+ </html>