ws_lite 0.10.0
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/.github/workflows/release.yml +29 -0
- data/.github/workflows/test.yml +27 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +56 -0
- data/CHANGELOG.md +102 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +11 -0
- data/lib/ws_lite/client.rb +122 -0
- data/lib/ws_lite/version.rb +3 -0
- data/lib/ws_lite.rb +11 -0
- data/sample/client.rb +30 -0
- data/sample/echo_server.rb +34 -0
- data/sample/webbrowser/index.html +22 -0
- data/sample/webbrowser/main.js +38 -0
- data/test/echo_server.rb +26 -0
- data/test/test_connect_block.rb +33 -0
- data/test/test_helper.rb +9 -0
- data/test/test_websocket_client_simple.rb +62 -0
- data/ws_lite.gemspec +38 -0
- metadata +193 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 28fa031e8efe42e3487d3c7103856b7d55c5489b747b884b4156b5600fff46b4
|
|
4
|
+
data.tar.gz: f2a7dadaa6e1ec0067fd69380fd2df0a39f967ca32123e046781778f8680426e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a2bb78f48cc431b8a9bddcd6968542d73ccc0d72037a6c4f088e63dafa9e01cffb8a6e25fee8086bee8a9b80a567e665e6fef73cffe311a983a498b40c7cb965
|
|
7
|
+
data.tar.gz: 291146dffd4a8278d29d93734d43945e443102ac14d5686c93b054b1c5be615886f8012bfe1082edf33f03511ff56f4c7915a5d84e8bdd3ce035f3d1df6714f2
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: GitHub Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Extract latest changelog section
|
|
18
|
+
run: |
|
|
19
|
+
# Extract content between the first and second ## x.y.z headers
|
|
20
|
+
awk '
|
|
21
|
+
/^## [0-9]/ { count++; if (count == 2) exit }
|
|
22
|
+
count == 1 { print }
|
|
23
|
+
' CHANGELOG.md > LATEST_CHANGES.md
|
|
24
|
+
|
|
25
|
+
- name: Create GitHub Release
|
|
26
|
+
uses: softprops/action-gh-release@v2
|
|
27
|
+
with:
|
|
28
|
+
generate_release_notes: false
|
|
29
|
+
body_path: LATEST_CHANGES.md
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
name: Ruby ${{ matrix.ruby }}
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
ruby:
|
|
17
|
+
- '3.4.3'
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Set up Ruby
|
|
22
|
+
uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
- name: Run the default task
|
|
27
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'test/**/*'
|
|
7
|
+
- 'sample/**/*'
|
|
8
|
+
- 'vendor/**/*' # Exclude vendored gems (CI bundles here)
|
|
9
|
+
|
|
10
|
+
# The upstream code style uses hash rockets and other conventions we preserve
|
|
11
|
+
Style/HashSyntax:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Style/StringLiterals:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
Style/Documentation:
|
|
18
|
+
Enabled: false
|
|
19
|
+
|
|
20
|
+
Style/FrozenStringLiteralComment:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
Metrics/MethodLength:
|
|
24
|
+
Max: 60
|
|
25
|
+
|
|
26
|
+
Metrics/ClassLength:
|
|
27
|
+
Enabled: false
|
|
28
|
+
|
|
29
|
+
Metrics/BlockLength:
|
|
30
|
+
Exclude:
|
|
31
|
+
- 'lib/ws_lite/client.rb'
|
|
32
|
+
- '*.gemspec'
|
|
33
|
+
|
|
34
|
+
Gemspec/DevelopmentDependencies:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
# The connect method is inherently complex (socket, SSL, handshake, read thread)
|
|
38
|
+
Metrics/AbcSize:
|
|
39
|
+
Exclude:
|
|
40
|
+
- 'lib/ws_lite/client.rb'
|
|
41
|
+
|
|
42
|
+
Metrics/CyclomaticComplexity:
|
|
43
|
+
Exclude:
|
|
44
|
+
- 'lib/ws_lite/client.rb'
|
|
45
|
+
|
|
46
|
+
Metrics/PerceivedComplexity:
|
|
47
|
+
Exclude:
|
|
48
|
+
- 'lib/ws_lite/client.rb'
|
|
49
|
+
|
|
50
|
+
# The two rescue branches for EPIPE and SSLError intentionally do the same thing
|
|
51
|
+
Lint/DuplicateBranch:
|
|
52
|
+
Exclude:
|
|
53
|
+
- 'lib/ws_lite/client.rb'
|
|
54
|
+
|
|
55
|
+
Layout/LineLength:
|
|
56
|
+
Max: 120
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
* Rename gem to `ws_lite` with `WSLite` module (successor to `websocket-client-simple`)
|
|
6
|
+
* Fix OpenSSL 3.x compatibility: enable `OP_IGNORE_UNEXPECTED_EOF` by default so that server-side TCP closes without TLS `close_notify` are treated as clean EOF instead of raising `SSL_read: unexpected eof while reading`
|
|
7
|
+
* Add `ssl_context` option to `connect` for passing a fully configured `OpenSSL::SSL::SSLContext`
|
|
8
|
+
* Extract `build_ssl_context` private method for cleaner SSL setup
|
|
9
|
+
* Fix infinite loop when `getc` returns nil on server disconnect (now emits `:close` and breaks) #27
|
|
10
|
+
* Fix read thread not terminating on SSL and IO errors (now emits `:close` and breaks instead of only `:error`) #35
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Prior releases were published as [`websocket-client-simple`](https://github.com/ruby-jp/websocket-client-simple).
|
|
15
|
+
|
|
16
|
+
## 0.9.0 - 2024-12-31
|
|
17
|
+
* Add mutex_m and base46 to runtime dependencies #31
|
|
18
|
+
|
|
19
|
+
## 0.8.0 - 2023-08-08
|
|
20
|
+
* Set `WebSocket.should_raise = true` https://github.com/ruby-jp/websocket-client-simple/pull/25 by @jlaffaye
|
|
21
|
+
|
|
22
|
+
## 0.7.0 - 2023-08-04
|
|
23
|
+
* Expose add accessor `Client#thread` #22 by @jlaffaye
|
|
24
|
+
* Rewrite changelog as a Markdown
|
|
25
|
+
|
|
26
|
+
## 0.6.1 - 2023-03-10
|
|
27
|
+
* Make `#open?` safe to use when `@handshake` is `nil` #20 by @cyberarm
|
|
28
|
+
|
|
29
|
+
## 0.6.0 - 2022-09-22
|
|
30
|
+
* Add option `cert_store` for passing cert store to SSLSocket context #12 by @DerekStride
|
|
31
|
+
* Set `OpenSSL::SSL::SSLSocket#sync_close` to `true` #12 by @DerekStride
|
|
32
|
+
|
|
33
|
+
## 0.5.1 - 2022-01-01
|
|
34
|
+
* Add `closed?` method to `WebSocket::Client::Simple` #8 by @fuyuton
|
|
35
|
+
* rescue when `OpenSSL::SSL::SSLError` raised #10 by @fuyuton
|
|
36
|
+
|
|
37
|
+
## 0.5.0 - 2021-12-31
|
|
38
|
+
* Change TLS context defaults to system's default. The previous defaults were `ssl_version=SSLv23` and `verify_mode=VERIFY_NONE`, which are insecure nowadays. #5
|
|
39
|
+
* thank you for contributing @eagletmt
|
|
40
|
+
* Made the necessary changes to use SNI. #6
|
|
41
|
+
* thank you for contributing @fuyuton
|
|
42
|
+
|
|
43
|
+
## 0.4.0 - 2021-12-30
|
|
44
|
+
* Drop support of ruby 2.5 or older versions as explicit.
|
|
45
|
+
|
|
46
|
+
## 0.3.1 - 2021-12-30
|
|
47
|
+
* The development of this repository has moved to https://github.com/ruby-jp/websocket-client-simple
|
|
48
|
+
|
|
49
|
+
## 0.3.0 - 2016-12-30
|
|
50
|
+
* `connect` method runs a given block before connecting WebSocket [#12](https://github.com/shokai/websocket-client-simple/issues/12)
|
|
51
|
+
* thank you for suggestion @codekitchen
|
|
52
|
+
|
|
53
|
+
## 0.2.5 - 2016-02-18
|
|
54
|
+
* bugfixed sending when broken pipe [#15](https://github.com/shokai/websocket-client-simple/pull/15)
|
|
55
|
+
* add `:verify_mode` option for SSL Context [#14](https://github.com/shokai/websocket-client-simple/pull/14)
|
|
56
|
+
* thank you for contributing @michaelvilensky
|
|
57
|
+
|
|
58
|
+
## 0.2.4 - 2015-11-12
|
|
59
|
+
* support handshake headers [#11](https://github.com/shokai/websocket-client-simple/pull/11)
|
|
60
|
+
* thank you for contributing @mathieugagne
|
|
61
|
+
|
|
62
|
+
## 0.2.3 - 2015-10-26
|
|
63
|
+
* kill thread at end of method [#10](https://github.com/shokai/websocket-client-simple/pull/10)
|
|
64
|
+
* thank you for contributing @hansy
|
|
65
|
+
|
|
66
|
+
## 0.2.2 - 2014-11-18
|
|
67
|
+
* bugfix socket reading
|
|
68
|
+
|
|
69
|
+
## 0.2.0 - 2014-06-07
|
|
70
|
+
* SSL support with `wss://` and `https://` scheme [#6](https://github.com/shokai/websocket-client-simple/pull/6)
|
|
71
|
+
* thank you for contributing @mallowlabs
|
|
72
|
+
|
|
73
|
+
## 0.1.0 - 2014-05-08
|
|
74
|
+
* add accessor `Client#handshake` [#5](https://github.com/shokai/websocket-client-simple/issues/5)
|
|
75
|
+
* bugfix socket reading
|
|
76
|
+
|
|
77
|
+
## 0.0.9 - 2014-04-03
|
|
78
|
+
* emit `error` in receive thread
|
|
79
|
+
* rescue only `Errno::EPIPE`, not all Errors
|
|
80
|
+
|
|
81
|
+
## 0.0.8 - 2014-01-29
|
|
82
|
+
* bugfix `Client#close` #4
|
|
83
|
+
|
|
84
|
+
## 0.0.7 - 2014-01-29
|
|
85
|
+
* send CLOSE frame in `Client#close` [#4](https://github.com/shokai/websocket-client-simple/issues/4)
|
|
86
|
+
## 0.0.6 - 2014-01-17
|
|
87
|
+
* add function `Client#open?`
|
|
88
|
+
|
|
89
|
+
## 0.0.5 - 2013-03-23
|
|
90
|
+
* kill read thread on close
|
|
91
|
+
|
|
92
|
+
## 0.0.4 - 2013-03-23
|
|
93
|
+
* fix sample and README
|
|
94
|
+
|
|
95
|
+
## 0.0.3 - 2013-03-23
|
|
96
|
+
* `:type => :text` option in send
|
|
97
|
+
|
|
98
|
+
## 0.0.2 - 2013-03-22
|
|
99
|
+
* remove unnecessary sleep
|
|
100
|
+
|
|
101
|
+
## 0.0.1 - 2013-03-22
|
|
102
|
+
* release
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013-2014 Sho Hashimoto
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
ws_lite
|
|
2
|
+
=======
|
|
3
|
+
[](https://rubygems.org/gems/ws_lite)
|
|
4
|
+
[](https://rubygems.org/gems/ws_lite)
|
|
5
|
+
[](https://github.com/carter2099/ws_lite/actions)
|
|
6
|
+
|
|
7
|
+
Successor to [websocket-client-simple](https://github.com/ruby-jp/websocket-client-simple) with OpenSSL 3.x fixes and improved connection reliability.
|
|
8
|
+
|
|
9
|
+
Installation
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
gem install ws_lite
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
-----
|
|
16
|
+
```ruby
|
|
17
|
+
require 'ws_lite'
|
|
18
|
+
|
|
19
|
+
ws = WSLite.connect 'ws://example.com:8888'
|
|
20
|
+
|
|
21
|
+
ws.on :message do |msg|
|
|
22
|
+
puts msg.data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ws.on :open do
|
|
26
|
+
ws.send 'hello!!!'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ws.on :close do |e|
|
|
30
|
+
p e
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ws.on :error do |e|
|
|
35
|
+
p e
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
loop do
|
|
39
|
+
ws.send STDIN.gets.strip
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`connect` runs a given block before connecting websocket
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
WSLite.connect 'ws://example.com:8888' do |ws|
|
|
47
|
+
ws.on :open do
|
|
48
|
+
puts "connect!"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
ws.on :message do |msg|
|
|
52
|
+
puts msg.data
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### SSL Options
|
|
58
|
+
|
|
59
|
+
Pass a custom `OpenSSL::SSL::SSLContext` for full control over TLS settings:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
63
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
64
|
+
|
|
65
|
+
ws = WSLite.connect 'wss://example.com', ssl_context: ctx
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
By default, `OP_IGNORE_UNEXPECTED_EOF` is enabled on OpenSSL 3.x to prevent `SSL_read: unexpected eof while reading` errors when servers close connections without a TLS `close_notify` alert.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Sample
|
|
72
|
+
------
|
|
73
|
+
[websocket chat](https://github.com/carter2099/ws_lite/tree/master/sample)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
Test
|
|
77
|
+
----
|
|
78
|
+
|
|
79
|
+
% gem install bundler
|
|
80
|
+
% bundle install
|
|
81
|
+
% export WS_PORT=8888
|
|
82
|
+
% rake test
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Contributing
|
|
86
|
+
------------
|
|
87
|
+
1. Fork it
|
|
88
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
89
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
90
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
91
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module WSLite
|
|
2
|
+
def self.connect(url, options = {})
|
|
3
|
+
client = ::WSLite::Client.new
|
|
4
|
+
yield client if block_given?
|
|
5
|
+
client.connect url, options
|
|
6
|
+
client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Client
|
|
10
|
+
include EventEmitter
|
|
11
|
+
|
|
12
|
+
attr_reader :url, :handshake, :thread
|
|
13
|
+
|
|
14
|
+
def connect(url, options = {})
|
|
15
|
+
return if @socket
|
|
16
|
+
|
|
17
|
+
@url = url
|
|
18
|
+
uri = URI.parse url
|
|
19
|
+
@socket = TCPSocket.new(uri.host,
|
|
20
|
+
uri.port || (uri.scheme == 'wss' ? 443 : 80))
|
|
21
|
+
if %w[https wss].include? uri.scheme
|
|
22
|
+
ctx = options[:ssl_context] || build_ssl_context(options)
|
|
23
|
+
@socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ctx)
|
|
24
|
+
@socket.sync_close = true
|
|
25
|
+
@socket.hostname = uri.host
|
|
26
|
+
@socket.connect
|
|
27
|
+
end
|
|
28
|
+
::WebSocket.should_raise = true
|
|
29
|
+
@handshake = ::WebSocket::Handshake::Client.new :url => url, :headers => options[:headers]
|
|
30
|
+
@handshaked = false
|
|
31
|
+
@pipe_broken = false
|
|
32
|
+
frame = ::WebSocket::Frame::Incoming::Client.new
|
|
33
|
+
@closed = false
|
|
34
|
+
once :__close do |err|
|
|
35
|
+
close
|
|
36
|
+
emit :close, err
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@thread = Thread.new do
|
|
40
|
+
until @closed
|
|
41
|
+
begin
|
|
42
|
+
unless (recv_data = @socket.getc)
|
|
43
|
+
emit :__close
|
|
44
|
+
break
|
|
45
|
+
end
|
|
46
|
+
if @handshaked
|
|
47
|
+
frame << recv_data
|
|
48
|
+
while (msg = frame.next)
|
|
49
|
+
emit :message, msg
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
@handshake << recv_data
|
|
53
|
+
if @handshake.finished?
|
|
54
|
+
@handshaked = true
|
|
55
|
+
emit :open
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
|
|
59
|
+
emit :__close, e
|
|
60
|
+
break
|
|
61
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
62
|
+
emit :error, e
|
|
63
|
+
emit :__close, e
|
|
64
|
+
break
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
emit :error, e
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@socket.write @handshake.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def send(data, opt = { :type => :text })
|
|
75
|
+
return if !@handshaked || @closed
|
|
76
|
+
|
|
77
|
+
type = opt[:type]
|
|
78
|
+
frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version)
|
|
79
|
+
begin
|
|
80
|
+
@socket.write frame.to_s
|
|
81
|
+
rescue Errno::EPIPE => e
|
|
82
|
+
@pipe_broken = true
|
|
83
|
+
emit :__close, e
|
|
84
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
85
|
+
@pipe_broken = true
|
|
86
|
+
emit :__close, e
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def close
|
|
91
|
+
return if @closed
|
|
92
|
+
|
|
93
|
+
send nil, :type => :close unless @pipe_broken
|
|
94
|
+
@closed = true
|
|
95
|
+
@socket&.close
|
|
96
|
+
@socket = nil
|
|
97
|
+
emit :__close
|
|
98
|
+
Thread.kill @thread if @thread
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def open?
|
|
102
|
+
@handshake&.finished? and !@closed
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def closed?
|
|
106
|
+
@closed
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def build_ssl_context(options)
|
|
112
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
113
|
+
ctx.ssl_version = options[:ssl_version] if options[:ssl_version]
|
|
114
|
+
ctx.verify_mode = options[:verify_mode] if options[:verify_mode]
|
|
115
|
+
ctx.options |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF if defined?(OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF)
|
|
116
|
+
cert_store = options[:cert_store] || OpenSSL::X509::Store.new
|
|
117
|
+
cert_store.set_default_paths
|
|
118
|
+
ctx.cert_store = cert_store
|
|
119
|
+
ctx
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
data/lib/ws_lite.rb
ADDED
data/sample/client.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
|
|
3
|
+
require 'ws_lite'
|
|
4
|
+
|
|
5
|
+
puts "ws_lite v#{WSLite::VERSION}"
|
|
6
|
+
|
|
7
|
+
url = ARGV.shift || 'ws://localhost:8080'
|
|
8
|
+
|
|
9
|
+
ws = WSLite.connect url
|
|
10
|
+
|
|
11
|
+
ws.on :message do |msg|
|
|
12
|
+
puts ">> #{msg.data}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ws.on :open do
|
|
16
|
+
puts "-- websocket open (#{ws.url})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
ws.on :close do |e|
|
|
20
|
+
puts "-- websocket close (#{e.inspect})"
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ws.on :error do |e|
|
|
25
|
+
puts "-- error (#{e.inspect})"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
loop do
|
|
29
|
+
ws.send STDIN.gets.strip
|
|
30
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'eventmachine'
|
|
3
|
+
require 'websocket-eventmachine-server'
|
|
4
|
+
|
|
5
|
+
PORT = (ARGV.shift || 8080).to_i
|
|
6
|
+
|
|
7
|
+
EM::run do
|
|
8
|
+
@channel = EM::Channel.new
|
|
9
|
+
|
|
10
|
+
puts "start websocket server - port:#{PORT}"
|
|
11
|
+
|
|
12
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => PORT) do |ws|
|
|
13
|
+
ws.onopen do
|
|
14
|
+
sid = @channel.subscribe do |mes|
|
|
15
|
+
ws.send mes
|
|
16
|
+
end
|
|
17
|
+
puts "<#{sid}> connect"
|
|
18
|
+
|
|
19
|
+
@channel.push "hello new client <#{sid}>"
|
|
20
|
+
|
|
21
|
+
ws.onmessage do |msg|
|
|
22
|
+
puts "<#{sid}> #{msg}"
|
|
23
|
+
@channel.push "<#{sid}> #{msg}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ws.onclose do
|
|
27
|
+
puts "<#{sid}> disconnected"
|
|
28
|
+
@channel.unsubscribe sid
|
|
29
|
+
@channel.push "<#{sid}> disconnected"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" content="text/html" http-equiv="Content-Type">
|
|
5
|
+
<title>websocket chat</title>
|
|
6
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
|
|
7
|
+
<script src="./main.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>websocket chat</h1>
|
|
11
|
+
<div>
|
|
12
|
+
<input id="name" size="15" type="text" value="NAME">
|
|
13
|
+
<input id="message" size="80" type="text" value="hello hello">
|
|
14
|
+
<input id="btn_post" type="button" value="post">
|
|
15
|
+
</div>
|
|
16
|
+
<ul id="chat"></ul>
|
|
17
|
+
<div id="footer">
|
|
18
|
+
<hr>
|
|
19
|
+
<a href="https://github.com/carter2099/ws_lite">https://github.com/carter2099/ws_lite</a>
|
|
20
|
+
</div>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var ws = new WebSocket("ws://localhost:8080");
|
|
2
|
+
|
|
3
|
+
ws.onmessage = function(e){
|
|
4
|
+
print(e.data);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
ws.onopen = function(e){
|
|
8
|
+
log("websocket open");
|
|
9
|
+
console.log(e);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
ws.onclose = function(e){
|
|
13
|
+
log("websocket close");
|
|
14
|
+
console.log(e);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
$(function(){
|
|
18
|
+
$("#btn_post").click(post);
|
|
19
|
+
$("#message").keydown(function(e){
|
|
20
|
+
if(e.keyCode == 13) post();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
var post = function(){
|
|
25
|
+
var name = $("#name").val();
|
|
26
|
+
var mes = $("#message").val();
|
|
27
|
+
ws.send(name+" : "+mes);
|
|
28
|
+
$("input#message").val("");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
var log = function(msg){
|
|
32
|
+
console.log(msg);
|
|
33
|
+
$("#chat").prepend($("<li>").text("[log] "+msg));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
var print = function(msg){
|
|
37
|
+
$("#chat").prepend($("<li>").text(msg));
|
|
38
|
+
};
|
data/test/echo_server.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module EchoServer
|
|
2
|
+
def self.start
|
|
3
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => self.port) do |ws|
|
|
4
|
+
@channel = EM::Channel.new
|
|
5
|
+
ws.onopen do
|
|
6
|
+
sid = @channel.subscribe do |mes|
|
|
7
|
+
ws.send mes # echo to client
|
|
8
|
+
end
|
|
9
|
+
ws.onmessage do |msg|
|
|
10
|
+
@channel.push msg
|
|
11
|
+
end
|
|
12
|
+
ws.onclose do
|
|
13
|
+
@channel.unsubscribe sid
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.port
|
|
20
|
+
(ENV['WS_PORT'] || 18080).to_i
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.url
|
|
24
|
+
"ws://localhost:#{self.port}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require File.expand_path 'test_helper', File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
class TestWSLite < Minitest::Test
|
|
4
|
+
|
|
5
|
+
def test_onopen
|
|
6
|
+
|
|
7
|
+
EM::run{
|
|
8
|
+
|
|
9
|
+
EchoServer.start
|
|
10
|
+
|
|
11
|
+
res = nil
|
|
12
|
+
|
|
13
|
+
EM::add_timer 1 do
|
|
14
|
+
WSLite.connect EchoServer.url do |client|
|
|
15
|
+
client.on :open do
|
|
16
|
+
client.send "hello world"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
client.on :message do |msg|
|
|
20
|
+
res = msg.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
EM::add_timer 2 do
|
|
26
|
+
assert_equal res, "hello world"
|
|
27
|
+
EM::stop_event_loop
|
|
28
|
+
end
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require_relative 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TestWSLite < Minitest::Test
|
|
4
|
+
|
|
5
|
+
def test_echo
|
|
6
|
+
msgs = ['foo','bar','baz']
|
|
7
|
+
res1 = []
|
|
8
|
+
res2 = []
|
|
9
|
+
|
|
10
|
+
EM::run{
|
|
11
|
+
EchoServer.start
|
|
12
|
+
|
|
13
|
+
## client1 --> server --> client2
|
|
14
|
+
EM::add_timer 1 do
|
|
15
|
+
client1 = WSLite.connect EchoServer.url
|
|
16
|
+
client2 = WSLite.connect EchoServer.url
|
|
17
|
+
assert_equal client1.open?, false
|
|
18
|
+
assert_equal client2.open?, false
|
|
19
|
+
|
|
20
|
+
client1.on :message do |msg|
|
|
21
|
+
res1.push msg
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
client2.on :message do |msg|
|
|
25
|
+
res2.push msg
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
client1.on :open do
|
|
29
|
+
msgs.each do |m|
|
|
30
|
+
client1.send m
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
client1.on :close do
|
|
35
|
+
EM::stop_event_loop
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
client2.on :close do
|
|
39
|
+
EM::stop_event_loop
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
EM::add_timer 3 do
|
|
43
|
+
assert_equal client1.open?, true
|
|
44
|
+
assert_equal client2.open?, true
|
|
45
|
+
client1.close
|
|
46
|
+
client2.close
|
|
47
|
+
EM::stop_event_loop
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
assert_equal msgs.size, res1.size
|
|
53
|
+
assert_equal msgs.size, res2.size
|
|
54
|
+
|
|
55
|
+
msgs.each_with_index do |msg,i|
|
|
56
|
+
assert_equal msg, res1[i].to_s
|
|
57
|
+
assert_equal msg, res2[i].to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
data/ws_lite.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'English'
|
|
2
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'ws_lite/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "ws_lite"
|
|
8
|
+
spec.version = WSLite::VERSION
|
|
9
|
+
spec.authors = ["Sho Hashimoto", "Yusuke Nakamura"]
|
|
10
|
+
spec.email = ["hashimoto@shokai.org", "yusuke1994525@gmail.com"]
|
|
11
|
+
spec.description = 'Simple WebSocket Client for Ruby'
|
|
12
|
+
spec.summary = spec.description
|
|
13
|
+
spec.homepage = "https://github.com/carter2099/ws_lite"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = '>= 3.4.0'
|
|
16
|
+
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/carter2099/ws_lite/blob/master/CHANGELOG.md"
|
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR).reject { |f| f == "Gemfile.lock" }
|
|
25
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
26
|
+
spec.require_paths = ["lib"]
|
|
27
|
+
|
|
28
|
+
spec.add_development_dependency "bundler"
|
|
29
|
+
spec.add_development_dependency "eventmachine"
|
|
30
|
+
spec.add_development_dependency "minitest"
|
|
31
|
+
spec.add_development_dependency "rake"
|
|
32
|
+
spec.add_development_dependency "websocket-eventmachine-server"
|
|
33
|
+
|
|
34
|
+
spec.add_dependency "base64"
|
|
35
|
+
spec.add_dependency "event_emitter"
|
|
36
|
+
spec.add_dependency "mutex_m"
|
|
37
|
+
spec.add_dependency "websocket"
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ws_lite
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.10.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sho Hashimoto
|
|
8
|
+
- Yusuke Nakamura
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: eventmachine
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: websocket-eventmachine-server
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: base64
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: event_emitter
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: mutex_m
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: websocket
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
description: Simple WebSocket Client for Ruby
|
|
140
|
+
email:
|
|
141
|
+
- hashimoto@shokai.org
|
|
142
|
+
- yusuke1994525@gmail.com
|
|
143
|
+
executables: []
|
|
144
|
+
extensions: []
|
|
145
|
+
extra_rdoc_files: []
|
|
146
|
+
files:
|
|
147
|
+
- ".github/workflows/release.yml"
|
|
148
|
+
- ".github/workflows/test.yml"
|
|
149
|
+
- ".gitignore"
|
|
150
|
+
- ".rubocop.yml"
|
|
151
|
+
- CHANGELOG.md
|
|
152
|
+
- Gemfile
|
|
153
|
+
- LICENSE.txt
|
|
154
|
+
- README.md
|
|
155
|
+
- Rakefile
|
|
156
|
+
- lib/ws_lite.rb
|
|
157
|
+
- lib/ws_lite/client.rb
|
|
158
|
+
- lib/ws_lite/version.rb
|
|
159
|
+
- sample/client.rb
|
|
160
|
+
- sample/echo_server.rb
|
|
161
|
+
- sample/webbrowser/index.html
|
|
162
|
+
- sample/webbrowser/main.js
|
|
163
|
+
- test/echo_server.rb
|
|
164
|
+
- test/test_connect_block.rb
|
|
165
|
+
- test/test_helper.rb
|
|
166
|
+
- test/test_websocket_client_simple.rb
|
|
167
|
+
- ws_lite.gemspec
|
|
168
|
+
homepage: https://github.com/carter2099/ws_lite
|
|
169
|
+
licenses:
|
|
170
|
+
- MIT
|
|
171
|
+
metadata:
|
|
172
|
+
homepage_uri: https://github.com/carter2099/ws_lite
|
|
173
|
+
source_code_uri: https://github.com/carter2099/ws_lite
|
|
174
|
+
changelog_uri: https://github.com/carter2099/ws_lite/blob/master/CHANGELOG.md
|
|
175
|
+
rubygems_mfa_required: 'true'
|
|
176
|
+
rdoc_options: []
|
|
177
|
+
require_paths:
|
|
178
|
+
- lib
|
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
180
|
+
requirements:
|
|
181
|
+
- - ">="
|
|
182
|
+
- !ruby/object:Gem::Version
|
|
183
|
+
version: 3.4.0
|
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
|
+
requirements:
|
|
186
|
+
- - ">="
|
|
187
|
+
- !ruby/object:Gem::Version
|
|
188
|
+
version: '0'
|
|
189
|
+
requirements: []
|
|
190
|
+
rubygems_version: 3.6.7
|
|
191
|
+
specification_version: 4
|
|
192
|
+
summary: Simple WebSocket Client for Ruby
|
|
193
|
+
test_files: []
|