tightrope 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 15a7366d3564b2186c12e87fd4b1f23c3fb92f288ea868e3d39cb21d97a41874
4
+ data.tar.gz: 219c19c58e24e601c4754cfdb782133156bbe947808612bc4a7eb9f5c0a13be9
5
+ SHA512:
6
+ metadata.gz: e70a010a40b5c1d1bdb0a0c66487acd72b650724a144e1dc32f71405a9c698f6bc8bdabebe6eab3b4101dfd12bc5dc7a08e47cc0a96ba3eb126d48981d263158
7
+ data.tar.gz: 420fbb1d5d74e583a9169b6d33e5ddcdd36f5b255feef928b76b2368c4dbe7ca7d5c8b874449eea93b1ea9ab7c033a860e4fbbe3cbe3cc234ae792a2b1907917
data/lib/tightrope.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "websocket-eventmachine-client"
2
+
3
+ require_relative 'tightrope/version'
4
+ require_relative 'tightrope/errors'
5
+ require_relative 'tightrope/websocket/filter_client'
6
+ require_relative 'tightrope/websocket/action_cable_client'
7
+ require_relative 'tightrope/websocket/action_cable_auth_client'
@@ -0,0 +1,12 @@
1
+ module Tightrope
2
+ ERROR_OK = 0
3
+ ERROR_PARAM_CHANNEL_MISSING = 1
4
+ ERROR_PARAM_MESSAGE_MISSING = 2
5
+ ERROR_PARAM_ACTION_MISSING = 3
6
+ ERROR_CHANNEL_ALREADY_SUBSCRIBED = 4
7
+ ERROR_CLIENT_HAS_NOTSUBSCRIBED = 5
8
+ ERROR_PARAM_CHANNEL_NOT_UNIQUE = 6
9
+
10
+ class LoginError < Exception
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Tightrope
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,50 @@
1
+ require 'http'
2
+
3
+ module Tightrope
4
+ module WebSocket
5
+ class ActionCableAuthClient < ::Tightrope::WebSocket::ActionCableClient
6
+ def self.connect(args)
7
+ local_args = prepare_args(args)
8
+ uri = URI.parse(local_args[:uri])
9
+
10
+ host = uri.host
11
+ port = uri.port
12
+
13
+ if args[:auth_uri]
14
+ auth_uri = URI.parse(local_args[:auth_uri])
15
+ host = auth_uri.host
16
+ port = auth_uri.port
17
+ local_args[:ssl] = true if auth_uri.scheme == 'https'
18
+ else
19
+ auth_path = local_args[:auth_path].sub(/^\//, '') if local_args[:auth_path]
20
+ local_args[:auth_uri] = (args[:ssl] ? 'https' : 'http') + "://#{host}"
21
+ local_args[:auth_uri] << ":#{port}" if port
22
+ local_args[:auth_uri] << "/#{auth_path}" if auth_path
23
+ end
24
+
25
+ local_args.delete(:auth_path)
26
+
27
+ check_arg(local_args, :user)
28
+ check_arg(local_args, :password)
29
+ local_args[:headers] = login(local_args)
30
+
31
+ super(local_args)
32
+ end
33
+
34
+ def self.login(args)
35
+ res = HTTP.post(args[:auth_uri], json: {email: args[:user], password: args[:password]})
36
+ raise Tightrope::LoginError.new("Login failed for user #{args[:user]}") if res.code != 200
37
+
38
+ {'access-token' => res.headers['access-token'],
39
+ 'expiry' => res.headers['expiry'],
40
+ 'uid' => res.headers['uid'],
41
+ 'client' => res.headers['client'],
42
+ 'token-type' => 'Bearer'}
43
+ end
44
+
45
+ def self.check_arg(args, key)
46
+ raise ArgumentError.new("Argument ':#{key.to_s}' is missing") unless args[key]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,148 @@
1
+ require 'cgi'
2
+ require 'json'
3
+
4
+ module Tightrope
5
+ module WebSocket
6
+ class ActionCableClient < Tightrope::WebSocket::FilterClient
7
+ attr_reader :channel
8
+
9
+ def initialize(args)
10
+ @channel = args[:channel] if args[:channel]
11
+ @channel_subscribed = false
12
+ super(args)
13
+ end
14
+
15
+ def channel_subscribed?
16
+ @channel_subscribed
17
+ end
18
+
19
+ # Connect to an ActionCable websocket server
20
+ # @param args [Hash] The request arguments
21
+ # @option args [String] :host The host IP/DNS name
22
+ # @option args [Integer] :port The port to connect too(default = 80)
23
+ # @option args [String] :uri Full URI for server(optional - use instead of host/port combination)
24
+ # @option args [Integer] :version Version of protocol to use(default = 13)
25
+ # @option args [Hash] :headers HTTP headers to use in the handshake
26
+ # @option args [Boolean] :ssl Force SSL/TLS connection
27
+ def self.connect(args = {})
28
+ super(prepare_args(args))
29
+ end
30
+
31
+ def self.prepare_args(args = {})
32
+ host = nil
33
+ port = nil
34
+ cable_path = nil
35
+ query = nil
36
+
37
+ if args[:uri]
38
+ uri = URI.parse(args[:uri])
39
+ host = uri.host
40
+ port = uri.port
41
+ args[:ssl] = true if uri.scheme == 'wss'
42
+ else
43
+ host = args[:host] if args[:host]
44
+ port = args[:port] if args[:port]
45
+ cable_path = args[:cable_path].sub(/^\//, '') if args[:cable_path]
46
+
47
+ args[:uri] = (args[:ssl] ? 'wss' : 'ws') + '://' + host
48
+ args[:uri] << ":#{port}" if port
49
+ args[:uri] << "/#{cable_path}" if cable_path
50
+ if args[:query]
51
+ args[:uri] << '?'
52
+ args[:query].each {|k,v| args[:uri] << ::CGI::escape(k.to_s) + '=' + ::CGI::escape(v.to_s) + '&'}
53
+ args[:uri].sub!(/&$/, '')
54
+ end
55
+
56
+ [:host, :port, :cable_path, :query].each {|k| args.delete(k) }
57
+ end
58
+
59
+ args
60
+ end
61
+
62
+ def subscribe_to_channel(channel = nil, options = {})
63
+ if channel_subscribed?
64
+ error(__method__, Tightrope::ERROR_CHANNEL_ALREADY_SUBSCRIBED)
65
+ return false
66
+ end
67
+
68
+ if @channel.nil? && channel.nil?
69
+ error(__method__, Tightrope::ERROR_PARAM_CHANNEL_MISSING)
70
+ return false
71
+ end
72
+
73
+ if @channel && channel && @channel != channel
74
+ error(__method__, Tightrope::ERROR_PARAM_CHANNEL_NOT_UNIQUE)
75
+ return false
76
+ end
77
+
78
+ @channel = channel if @channel.nil? && channel
79
+
80
+ send_message(command: 'subscribe', channel: @channel, opts: options)
81
+ end
82
+
83
+ def send_message(options = {})
84
+ channel = options[:channel]
85
+ unless channel
86
+ error(__method__, Tightrope::ERROR_PARAM_CHANNEL_MISSING)
87
+ return false
88
+ end
89
+
90
+ identifier = {channel: channel}
91
+ identifier.merge!(options[:opts]) if options[:opts]
92
+ msg = {command: options[:command] || 'message', identifier: identifier.to_json}
93
+
94
+ if options[:message] || options[:action]
95
+ unless options[:message]
96
+ error(__method__, Tightrope::ERROR_PARAM_MESSAGE_MISSING)
97
+ return false
98
+ end
99
+ unless options[:action]
100
+ error(__method__, Tightrope::ERROR_PARAM_ACTION_MISSING)
101
+ return false
102
+ end
103
+ msg[:data] = {message: options[:message], action: options[:action]}.to_json
104
+ end
105
+
106
+ send(msg.to_json, type: options['type'] || 'text')
107
+ end
108
+
109
+ def send_to_channel(options = {})
110
+ unless channel_subscribed?
111
+ error(__method__, Tightrope::ERROR_CLIENT_HAS_NOTSUBSCRIBED)
112
+ return false
113
+ end
114
+
115
+ send_message(options.merge(channel: @channel))
116
+ end
117
+
118
+ def trigger_onsubscription(channel)
119
+ @onsubscription.call(channel) if @onsubscription
120
+ end
121
+
122
+ #
123
+ # is called when the subscription confirmation is sent
124
+ #
125
+ def onsubscription(&blk)
126
+ @onsubscription = blk
127
+ end
128
+
129
+ #
130
+ # triggers event 'onsubscribtion' when subscription confirmation is sent
131
+ #
132
+ def filter_text(frame)
133
+ unless channel_subscribed?
134
+ confirmation = JSON.parse(frame.to_s)
135
+ if confirmation['type'] == 'confirm_subscription'
136
+ identifier = JSON.parse(confirmation['identifier'])
137
+ @channel_subscribed = @channel && identifier['channel'] == @channel
138
+ trigger_onsubscription(identifier['channel']) if @channel_subscribed
139
+ return @channel_subscribed
140
+ end
141
+ end
142
+ false
143
+ end
144
+
145
+ end
146
+ end # ActionCableClient
147
+
148
+ end
@@ -0,0 +1,87 @@
1
+ require 'json'
2
+
3
+ module Tightrope
4
+ module WebSocket
5
+ # https://tools.ietf.org/html/rfc6455
6
+
7
+ class FilterClient < ::WebSocket::EventMachine::Client
8
+ #
9
+ # Websocket client for filtering frames send by websocket events.
10
+ # You can customize the methods
11
+ # filter_text, filter_binary, filter_ping, filter_pong and filter_close
12
+ #
13
+ def initialize(args)
14
+ create_methods
15
+ super(args)
16
+ end
17
+
18
+ [:text, :binary, :ping, :pong, :close].each do |type|
19
+ define_method("filter_#{type.to_s}") { |arg| false }
20
+ end
21
+
22
+ def args
23
+ instance_variable_get("@args")
24
+ end
25
+
26
+ def error(method, code, message = '')
27
+ e = {method: method, code: code}
28
+ e[:message] = message if message && message != ''
29
+ trigger_onerror(e.to_json)
30
+ end
31
+
32
+ private
33
+
34
+ def create_method(name, &block)
35
+ self.class.send(:define_method, name, &block)
36
+ end
37
+
38
+ def create_methods
39
+ [:onopen, :onping, :onpong, :onerror, :onmessage, :onclose].each do |event|
40
+ create_method("trigger_#{event}".to_sym) do |data,type=nil|
41
+ ivar = instance_variable_get("@#{event}")
42
+ ivar.call(data,type) if ivar
43
+ end
44
+ end
45
+
46
+ create_method(:handle_open) do |data|
47
+ frame_super = instance_variable_get("@frame")
48
+ frame_super << data
49
+ instance_variable_set("@frame", frame_super)
50
+ while frame = frame_super.next
51
+ state = instance_variable_get("@state")
52
+ if state == :open
53
+ case frame.type
54
+ when :close
55
+ state = :closing
56
+ instance_variable_set("@state", :closing)
57
+ close
58
+ trigger_onclose(frame.code, frame.data) unless filter_close(frame)
59
+ when :ping
60
+ pong(frame.to_s)
61
+ trigger_onping(frame.to_s) unless filter_ping(frame)
62
+ when :pong
63
+ trigger_onpong(frame.to_s) unless filter_pong(frame)
64
+ when :text
65
+ trigger_onmessage(frame.to_s, :text) unless filter_text(frame)
66
+ when :binary
67
+ trigger_onmessage(frame.to_s, :binary) unless filter_binary(frame)
68
+ end
69
+ else
70
+ break
71
+ end
72
+ end
73
+
74
+ if frame_super.error?
75
+ error_code = case error
76
+ when :invalid_payload_encoding then 1007
77
+ else 1002
78
+ end
79
+ trigger_onerror(error.to_s)
80
+ close(error_code)
81
+ unbind
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tightrope
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Andreas Leipelt
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-27 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: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: websocket-eventmachine-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: http
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
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
+ description: tigthrope
98
+ email:
99
+ - andreas@leipelt-hamburg.de
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - lib/tightrope.rb
105
+ - lib/tightrope/errors.rb
106
+ - lib/tightrope/version.rb
107
+ - lib/tightrope/websocket/action_cable_auth_client.rb
108
+ - lib/tightrope/websocket/action_cable_client.rb
109
+ - lib/tightrope/websocket/filter_client.rb
110
+ homepage: https://github.com/briareos62/tightrope
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.7.6
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Rails ActionCable client library for use in non browser clients. Based on
134
+ Eventmachine.
135
+ test_files: []