tightrope 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []