socket2me 0.5.1
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +7 -0
- data/example/counter.ru +52 -0
- data/lib/socket2me/configuration.rb +40 -0
- data/lib/socket2me/middleware/add_script_tag.rb +75 -0
- data/lib/socket2me/version.rb +3 -0
- data/lib/socket2me/ws_server.rb +60 -0
- data/lib/socket2me.rb +15 -0
- data/socket2me.gemspec +26 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f123c415850bb1bb171b3927e2b50bda54ef348e
|
4
|
+
data.tar.gz: 6fcc758271404f0e3ccb7e0aea791df36ac33b18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 654b89e9ea16c80b880c1535f24a6d6d111124d8d6d186bbdb554daf606f913f353ae12c995b429d36d0879308fd77183709d6f3e725f4e938a58f527c8ae59c
|
7
|
+
data.tar.gz: 530abc92eafcee8ebba2aa4ee94b099132ef1c4b0431bd6ba4cdee1e5ae19595e0aef56445deeda7ca15406d094262a37660ae6e8ace6b83ae39dd0a3f757722
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Scott Pierce
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Socket2me
|
2
|
+
|
3
|
+
Execute Javascript in the browser from the server using WebSockets.
|
4
|
+
|
5
|
+
This is a proof of concept. There are no attempts to secure this feature or
|
6
|
+
even make it practical in a multi-user environment. That's right, any browser
|
7
|
+
that is viewing the site is going to get the same javascript executed on it.
|
8
|
+
This gem should really only ever be used in development.
|
9
|
+
|
10
|
+
The original use for this was to send all logs to browser as it was appended.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
def pipe_to_browser(src)
|
14
|
+
File.open(src,"r") do |f|
|
15
|
+
f.seek(0,IO::SEEK_END)
|
16
|
+
while true do
|
17
|
+
IO.select([f]) # wait for change
|
18
|
+
line = f.gets
|
19
|
+
Socket2me.exec_js "$('#logs').append(#{line.to_json})"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
Thread.new{pipe_to_browser('log/development.log')}
|
24
|
+
```
|
25
|
+
|
26
|
+
Please don't run this in production. Ever.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
group :development, :test do
|
34
|
+
gem 'socket2me'
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
And then execute:
|
39
|
+
|
40
|
+
$ bundle
|
41
|
+
|
42
|
+
Or install it yourself as:
|
43
|
+
|
44
|
+
$ gem install socket2me
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
A full rack example is in `example/counter.ru`. That app simply creates
|
49
|
+
an HTML page and provides a helper js function to display some output. The
|
50
|
+
example then outputs the server time a counter every second in a background
|
51
|
+
thread.
|
52
|
+
|
53
|
+
To run `example/counter.rb`:
|
54
|
+
|
55
|
+
1. checkout the project: `git clone https://github.com/ddrscott/socket2me`
|
56
|
+
2. go into the the socket2me directory: `cd socket2me`
|
57
|
+
3. install requirements: `./bin/setup`
|
58
|
+
4. start a rack server with the example: `rackup example/counter.ru`
|
59
|
+
|
60
|
+
### Rack
|
61
|
+
```ruby
|
62
|
+
# config.ru
|
63
|
+
use SocketToMe::Middleware::AddScriptTag
|
64
|
+
```
|
65
|
+
|
66
|
+
### Rails
|
67
|
+
```ruby
|
68
|
+
# config/environments/development.rb
|
69
|
+
config.middleware.use SocketToMe::Middleware::AddScriptTag
|
70
|
+
```
|
71
|
+
|
72
|
+
### Execute Javascript
|
73
|
+
```ruby
|
74
|
+
# elsewhere.rb
|
75
|
+
SocketToMe.exec_js "alert('hello world!')"
|
76
|
+
```
|
77
|
+
|
78
|
+
## Development
|
79
|
+
|
80
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
81
|
+
|
82
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
83
|
+
|
84
|
+
Spot check everything works by running `rackup example/counter.ru`. The browser
|
85
|
+
should continually append a timestamp and counter below the header.
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ddrscott/socket2me.
|
90
|
+
|
91
|
+
|
92
|
+
## License
|
93
|
+
|
94
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
95
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/example/counter.ru
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'socket2me'
|
4
|
+
|
5
|
+
class Stub
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
Socket2me.configure do |config|
|
9
|
+
config.ws_host = '0.0.0.0'
|
10
|
+
config.ws_port = '3002'
|
11
|
+
end
|
12
|
+
|
13
|
+
Socket2me.start_ws_server
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(_)
|
17
|
+
[200, {'Content-Type' => 'text/html'}, [<<-HTML]]
|
18
|
+
<html>
|
19
|
+
<body>
|
20
|
+
<h1>Stub <small>generated by #{__FILE__}</small></h1>
|
21
|
+
<script>
|
22
|
+
function appendP(text) {
|
23
|
+
var p = document.createElement("p");
|
24
|
+
p.innerHTML = text;
|
25
|
+
document.body.appendChild(p);
|
26
|
+
}
|
27
|
+
</script>
|
28
|
+
</body>
|
29
|
+
</html>
|
30
|
+
HTML
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_counter
|
34
|
+
Thread.abort_on_exception = true
|
35
|
+
Thread.new do
|
36
|
+
counter = 0
|
37
|
+
while true
|
38
|
+
sleep 1
|
39
|
+
Socket2me.exec_js("appendP('[#{Time.now}]: #{counter += 1}')")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
use Socket2me::Middleware::AddScriptTag
|
46
|
+
|
47
|
+
stub = Stub.new
|
48
|
+
stub.start_counter
|
49
|
+
|
50
|
+
run stub
|
51
|
+
|
52
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Socket2me
|
4
|
+
|
5
|
+
class Configuration
|
6
|
+
# @attr [String] ws_host for the WebSocket listener
|
7
|
+
attr_accessor :ws_host
|
8
|
+
|
9
|
+
# @attr [String] ws_port for the WebSocket listener
|
10
|
+
attr_accessor :ws_port
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@ws_host = '0.0.0.0'
|
14
|
+
@ws_port = '3001'
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Logger] a basic STDOUT logger if one hasn't been set
|
18
|
+
def logger
|
19
|
+
@logger ||= Logger.new(STDOUT)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Logger] logger to use
|
23
|
+
def logger=(logger)
|
24
|
+
@logger = logger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @yield [Configuration]
|
29
|
+
def self.configure(&block)
|
30
|
+
@config ||= Configuration.new
|
31
|
+
if block_given?
|
32
|
+
block.call @config
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Configuration] current configuration
|
37
|
+
def self.config
|
38
|
+
@config
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'erb'
|
3
|
+
require 'em-websocket'
|
4
|
+
|
5
|
+
module Socket2me
|
6
|
+
module Middleware
|
7
|
+
class AddScriptTag
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
@ws_url = "ws://#{Socket2me.config.ws_host}:#{Socket2me.config.ws_port}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# 1. look for a text/html response type from parent app.
|
15
|
+
# 2. replace the closing `</body>` with the WebSocket client code.
|
16
|
+
#
|
17
|
+
# @param [Hash] env about Rack environment and the request
|
18
|
+
# @return [String, Hash, Array[String]]
|
19
|
+
def call(env)
|
20
|
+
response = @app.call(env)
|
21
|
+
status, headers, body = *response
|
22
|
+
|
23
|
+
return response unless headers['Content-Type'] == 'text/html'
|
24
|
+
|
25
|
+
full_body = body.join
|
26
|
+
|
27
|
+
# replace the last body with script
|
28
|
+
full_body.gsub!(%r{(</body>)}i,"#{script_tag}\\1")
|
29
|
+
|
30
|
+
return status, headers, [full_body]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] the outer Javascript tag with WS client script
|
34
|
+
def script_tag
|
35
|
+
js = ERB.new(js_erb).result(binding)
|
36
|
+
"<script>#{js}</script>"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adjust the javascript client code as needed.
|
40
|
+
# @return [String] inner javascript contents.
|
41
|
+
def js_erb
|
42
|
+
<<-JS
|
43
|
+
(function () {
|
44
|
+
var socket = new WebSocket("<%= @ws_url %>");
|
45
|
+
|
46
|
+
function log(message) {
|
47
|
+
console.log.apply(console, arguments);
|
48
|
+
}
|
49
|
+
|
50
|
+
try {
|
51
|
+
socket.onopen = function () {
|
52
|
+
log("Socket Status: " + socket.readyState + " (open)");
|
53
|
+
};
|
54
|
+
|
55
|
+
socket.onclose = function () {
|
56
|
+
log("Socket Status: " + socket.readyState + " (closed)");
|
57
|
+
};
|
58
|
+
|
59
|
+
socket.onmessage = function (msg) {
|
60
|
+
try {
|
61
|
+
// execute in an anonymous function
|
62
|
+
new Function(msg.data)();
|
63
|
+
} catch (msgEx){
|
64
|
+
console.error("Could not execute `%s` due to %o", msg.data, msgEx);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
} catch (exception) {
|
68
|
+
console.error("Error: " + exception);
|
69
|
+
}
|
70
|
+
})();
|
71
|
+
JS
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'singleton'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# Singleton wrapper class around an EM::WebSocket thread.
|
6
|
+
module Socket2me
|
7
|
+
class WsServer
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
# initializes an empty client list
|
11
|
+
def initialize
|
12
|
+
@clients = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# maps `Socket2me#config` options to `EM::WebSocket` options
|
16
|
+
# @return [Hash] hash of options compatible with EM::WebSocket#start
|
17
|
+
def ws_options
|
18
|
+
{
|
19
|
+
host: Socket2me.config.ws_host,
|
20
|
+
port: Socket2me.config.ws_port
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# sends a message to all connected clients
|
25
|
+
# @param [String] msg to send to all the clients
|
26
|
+
def send_to_clients(msg)
|
27
|
+
EM.schedule do
|
28
|
+
@clients.each do |socket|
|
29
|
+
socket.send msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def log(text)
|
35
|
+
Socket2me.config.logger.debug "[WsServer] #{text}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Start the EM::WebSocket thread.
|
39
|
+
# The server
|
40
|
+
def start
|
41
|
+
@thread = Thread.new do
|
42
|
+
EM::WebSocket.start(ws_options) do |ws|
|
43
|
+
ws.onopen do |handshake|
|
44
|
+
@clients << ws
|
45
|
+
log "onopen: #{handshake.headers}"
|
46
|
+
end
|
47
|
+
|
48
|
+
ws.onclose do |event|
|
49
|
+
log "closed: #{event}"
|
50
|
+
@clients.delete ws
|
51
|
+
end
|
52
|
+
|
53
|
+
ws.onmessage do |msg|
|
54
|
+
log "received: #{msg}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/socket2me.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'socket2me/version'
|
2
|
+
require 'socket2me/configuration'
|
3
|
+
require 'socket2me/middleware/add_script_tag'
|
4
|
+
require 'socket2me/ws_server'
|
5
|
+
|
6
|
+
module Socket2me
|
7
|
+
|
8
|
+
def self.start_ws_server
|
9
|
+
WsServer.instance.start
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.exec_js(js)
|
13
|
+
WsServer.instance.send_to_clients(js)
|
14
|
+
end
|
15
|
+
end
|
data/socket2me.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'socket2me/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "socket2me"
|
8
|
+
spec.version = Socket2me::VERSION
|
9
|
+
spec.authors = ["Scott Pierce"]
|
10
|
+
spec.email = ["ddrscott@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Execute Javascript in the browser from the server using WebSockets}
|
13
|
+
spec.homepage = "https://github.com/ddrscott/socket2me"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency 'rack', '~> 1.6'
|
20
|
+
spec.add_dependency 'em-websocket', '~> 0.5'
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socket2me
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Pierce
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-websocket
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- ddrscott@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- example/counter.ru
|
114
|
+
- lib/socket2me.rb
|
115
|
+
- lib/socket2me/configuration.rb
|
116
|
+
- lib/socket2me/middleware/add_script_tag.rb
|
117
|
+
- lib/socket2me/version.rb
|
118
|
+
- lib/socket2me/ws_server.rb
|
119
|
+
- socket2me.gemspec
|
120
|
+
homepage: https://github.com/ddrscott/socket2me
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.4.5.1
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Execute Javascript in the browser from the server using WebSockets
|
144
|
+
test_files: []
|
145
|
+
has_rdoc:
|