sockudo 1.0.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/CHANGELOG.md +137 -0
- data/LICENSE +20 -0
- data/README.md +289 -0
- data/lib/sockudo/channel.rb +179 -0
- data/lib/sockudo/client.rb +593 -0
- data/lib/sockudo/request.rb +112 -0
- data/lib/sockudo/resource.rb +36 -0
- data/lib/sockudo/utils.rb +34 -0
- data/lib/sockudo/version.rb +3 -0
- data/lib/sockudo/webhook.rb +110 -0
- data/lib/sockudo.rb +77 -0
- metadata +208 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Sockudo
|
|
2
|
+
class Resource
|
|
3
|
+
def initialize(client, path)
|
|
4
|
+
@client = client
|
|
5
|
+
@path = path
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get(params)
|
|
9
|
+
create_request(:get, params).send_sync
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_async(params)
|
|
13
|
+
create_request(:get, params).send_async
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def post(params, headers = {})
|
|
17
|
+
body = MultiJson.encode(params)
|
|
18
|
+
create_request(:post, {}, body, headers).send_sync
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post_async(params, headers = {})
|
|
22
|
+
body = MultiJson.encode(params)
|
|
23
|
+
create_request(:post, {}, body, headers).send_async
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def create_request(verb, params, body = nil, extra_headers = {})
|
|
29
|
+
Request.new(@client, verb, url, params, body, extra_headers)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def url
|
|
33
|
+
@_url ||= @client.url(@path)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Sockudo
|
|
2
|
+
module Utils
|
|
3
|
+
def validate_socket_id(socket_id)
|
|
4
|
+
unless socket_id && /\A\d+\.\d+\z/.match(socket_id)
|
|
5
|
+
raise Sockudo::Error, "Invalid socket ID #{socket_id.inspect}"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Compute authentication string required as part of the user authentication
|
|
10
|
+
# and subscription authorization endpoints responses.
|
|
11
|
+
# Generally the authenticate method should be used in preference to this one.
|
|
12
|
+
#
|
|
13
|
+
# @param socket_id [String] Each Sockudo socket connection receives a
|
|
14
|
+
# unique socket_id. This is sent from sockudo.js to your server when
|
|
15
|
+
# channel authentication is required.
|
|
16
|
+
# @param custom_string [String] Allows signing additional data
|
|
17
|
+
# @return [String]
|
|
18
|
+
#
|
|
19
|
+
# @raise [Sockudo::Error] if socket_id or custom_string invalid
|
|
20
|
+
#
|
|
21
|
+
def _authentication_string(socket_id, string_to_sign, token, custom_string = nil)
|
|
22
|
+
validate_socket_id(socket_id)
|
|
23
|
+
|
|
24
|
+
raise Sockudo::Error, 'Custom argument must be a string' unless custom_string.nil? || custom_string.is_a?(String)
|
|
25
|
+
|
|
26
|
+
Sockudo.logger.debug "Signing #{string_to_sign}"
|
|
27
|
+
|
|
28
|
+
digest = OpenSSL::Digest.new('SHA256')
|
|
29
|
+
signature = OpenSSL::HMAC.hexdigest(digest, token.secret, string_to_sign)
|
|
30
|
+
|
|
31
|
+
"#{token.key}:#{signature}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'multi_json'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
|
|
4
|
+
module Sockudo
|
|
5
|
+
# Used to parse and authenticate WebHooks
|
|
6
|
+
#
|
|
7
|
+
# @example Sinatra
|
|
8
|
+
# post '/webhooks' do
|
|
9
|
+
# webhook = Sockudo::WebHook.new(request)
|
|
10
|
+
# if webhook.valid?
|
|
11
|
+
# webhook.events.each do |event|
|
|
12
|
+
# case event["name"]
|
|
13
|
+
# when 'channel_occupied'
|
|
14
|
+
# puts "Channel occupied: #{event["channel"]}"
|
|
15
|
+
# when 'channel_vacated'
|
|
16
|
+
# puts "Channel vacated: #{event["channel"]}"
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
# else
|
|
20
|
+
# status 401
|
|
21
|
+
# end
|
|
22
|
+
# return
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class WebHook
|
|
26
|
+
attr_reader :key, :signature
|
|
27
|
+
|
|
28
|
+
# Provide either a Rack::Request or a Hash containing :key, :signature,
|
|
29
|
+
# :body, and :content_type (optional)
|
|
30
|
+
#
|
|
31
|
+
def initialize(request, client = Sockudo)
|
|
32
|
+
@client = client
|
|
33
|
+
# For Rack::Request and ActionDispatch::Request
|
|
34
|
+
if request.respond_to?(:env) && request.respond_to?(:content_type)
|
|
35
|
+
@key = request.env['HTTP_X_PUSHER_KEY']
|
|
36
|
+
@signature = request.env["HTTP_X_PUSHER_SIGNATURE"]
|
|
37
|
+
@content_type = request.content_type
|
|
38
|
+
|
|
39
|
+
request.body.rewind
|
|
40
|
+
@body = request.body.read
|
|
41
|
+
request.body.rewind
|
|
42
|
+
else
|
|
43
|
+
@key, @signature, @body = request.values_at(:key, :signature, :body)
|
|
44
|
+
@content_type = request[:content_type] || 'application/json'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns whether the WebHook is valid by checking that the signature
|
|
49
|
+
# matches the configured key & secret. In the case that the webhook is
|
|
50
|
+
# invalid, the reason is logged
|
|
51
|
+
#
|
|
52
|
+
# @param extra_tokens [Hash] If you have extra tokens for your Sockudo app, you can specify them so that they're used to attempt validation.
|
|
53
|
+
#
|
|
54
|
+
def valid?(extra_tokens = nil)
|
|
55
|
+
extra_tokens = [extra_tokens] if extra_tokens.kind_of?(Hash)
|
|
56
|
+
if @key == @client.key
|
|
57
|
+
return check_signature(@client.secret)
|
|
58
|
+
elsif extra_tokens
|
|
59
|
+
extra_tokens.each do |token|
|
|
60
|
+
return check_signature(token[:secret]) if @key == token[:key]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
Sockudo.logger.warn "Received webhook with unknown key: #{key}"
|
|
64
|
+
return false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Array of events (as Hashes) contained inside the webhook
|
|
68
|
+
#
|
|
69
|
+
def events
|
|
70
|
+
data["events"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The time at which the WebHook was initially triggered by Sockudo, i.e.
|
|
74
|
+
# when the event occurred
|
|
75
|
+
#
|
|
76
|
+
# @return [Time]
|
|
77
|
+
#
|
|
78
|
+
def time
|
|
79
|
+
Time.at(data["time_ms"].to_f/1000)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Access the parsed WebHook body
|
|
83
|
+
#
|
|
84
|
+
def data
|
|
85
|
+
@data ||= begin
|
|
86
|
+
case @content_type
|
|
87
|
+
when 'application/json'
|
|
88
|
+
MultiJson.decode(@body)
|
|
89
|
+
else
|
|
90
|
+
raise "Unknown Content-Type (#{@content_type})"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Checks signature against secret and returns boolean
|
|
98
|
+
#
|
|
99
|
+
def check_signature(secret)
|
|
100
|
+
digest = OpenSSL::Digest::SHA256.new
|
|
101
|
+
expected = OpenSSL::HMAC.hexdigest(digest, secret, @body)
|
|
102
|
+
if @signature == expected
|
|
103
|
+
return true
|
|
104
|
+
else
|
|
105
|
+
Sockudo.logger.warn "Received WebHook with invalid signature: got #{@signature}, expected #{expected}"
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/sockudo.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
autoload 'Logger', 'logger'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
require 'sockudo/utils'
|
|
7
|
+
require 'sockudo/client'
|
|
8
|
+
|
|
9
|
+
# Used for configuring API credentials and creating Channel objects
|
|
10
|
+
#
|
|
11
|
+
module Sockudo
|
|
12
|
+
# All errors descend from this class so they can be easily rescued
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# begin
|
|
16
|
+
# Sockudo.trigger('channel_name', 'event_name, {:some => 'data'})
|
|
17
|
+
# rescue Sockudo::Error => e
|
|
18
|
+
# # Do something on error
|
|
19
|
+
# end
|
|
20
|
+
class Error < RuntimeError; end
|
|
21
|
+
class AuthenticationError < Error; end
|
|
22
|
+
class ConfigurationError < Error
|
|
23
|
+
def initialize(key)
|
|
24
|
+
super "missing key `#{key}' in the client configuration"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
class HTTPError < Error; attr_accessor :original_error; end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
extend Forwardable
|
|
31
|
+
|
|
32
|
+
def_delegators :default_client, :scheme, :host, :port, :app_id, :key,
|
|
33
|
+
:secret, :http_proxy, :encryption_master_key_base64
|
|
34
|
+
def_delegators :default_client, :scheme=, :host=, :port=, :app_id=, :key=,
|
|
35
|
+
:secret=, :http_proxy=, :encryption_master_key_base64=
|
|
36
|
+
|
|
37
|
+
def_delegators :default_client, :authentication_token, :url, :cluster
|
|
38
|
+
def_delegators :default_client, :encrypted=, :url=, :cluster=
|
|
39
|
+
def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
|
|
40
|
+
|
|
41
|
+
def_delegators :default_client, :get, :get_async, :post, :post_async
|
|
42
|
+
def_delegators :default_client, :channels, :channel_info, :channel_users
|
|
43
|
+
def_delegators :default_client, :trigger, :trigger_batch, :trigger_async, :trigger_batch_async
|
|
44
|
+
def_delegators :default_client, :authenticate, :webhook, :channel, :[]
|
|
45
|
+
def_delegators :default_client, :notify
|
|
46
|
+
|
|
47
|
+
# Generate a unique idempotency key (UUID v4) for use with trigger methods.
|
|
48
|
+
#
|
|
49
|
+
# @return [String] A UUID string
|
|
50
|
+
def generate_idempotency_key
|
|
51
|
+
SecureRandom.uuid
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
attr_writer :logger
|
|
55
|
+
|
|
56
|
+
def logger
|
|
57
|
+
@logger ||= begin
|
|
58
|
+
log = Logger.new($stdout)
|
|
59
|
+
log.level = Logger::INFO
|
|
60
|
+
log
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def default_client
|
|
65
|
+
@default_client ||= begin
|
|
66
|
+
cli = Sockudo::Client
|
|
67
|
+
ENV['SOCKUDO_URL'] ? cli.from_env : cli.new
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
require 'sockudo/version'
|
|
74
|
+
require 'sockudo/channel'
|
|
75
|
+
require 'sockudo/request'
|
|
76
|
+
require 'sockudo/resource'
|
|
77
|
+
require 'sockudo/webhook'
|
metadata
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sockudo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sockudo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: multi_json
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.15'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.15'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: pusher-signature
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.1.8
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.1.8
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: httpclient
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.8'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.8'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.9'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.9'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: webmock
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.9'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.9'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: em-http-request
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.1'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.1'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: addressable
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '2.7'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '2.7'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '13.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '13.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rack
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '2.2'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '2.2'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: json
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '2.3'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '2.3'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: rbnacl
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '7.1'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '7.1'
|
|
167
|
+
description: Wrapper for Sockudo REST API
|
|
168
|
+
email:
|
|
169
|
+
- support@sockudo.com
|
|
170
|
+
executables: []
|
|
171
|
+
extensions: []
|
|
172
|
+
extra_rdoc_files: []
|
|
173
|
+
files:
|
|
174
|
+
- CHANGELOG.md
|
|
175
|
+
- LICENSE
|
|
176
|
+
- README.md
|
|
177
|
+
- lib/sockudo.rb
|
|
178
|
+
- lib/sockudo/channel.rb
|
|
179
|
+
- lib/sockudo/client.rb
|
|
180
|
+
- lib/sockudo/request.rb
|
|
181
|
+
- lib/sockudo/resource.rb
|
|
182
|
+
- lib/sockudo/utils.rb
|
|
183
|
+
- lib/sockudo/version.rb
|
|
184
|
+
- lib/sockudo/webhook.rb
|
|
185
|
+
homepage: http://github.com/sockudo/sockudo-http-ruby
|
|
186
|
+
licenses:
|
|
187
|
+
- MIT
|
|
188
|
+
metadata: {}
|
|
189
|
+
post_install_message:
|
|
190
|
+
rdoc_options: []
|
|
191
|
+
require_paths:
|
|
192
|
+
- lib
|
|
193
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
|
+
requirements:
|
|
195
|
+
- - ">="
|
|
196
|
+
- !ruby/object:Gem::Version
|
|
197
|
+
version: '2.6'
|
|
198
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
|
+
requirements:
|
|
200
|
+
- - ">="
|
|
201
|
+
- !ruby/object:Gem::Version
|
|
202
|
+
version: '0'
|
|
203
|
+
requirements: []
|
|
204
|
+
rubygems_version: 3.4.20
|
|
205
|
+
signing_key:
|
|
206
|
+
specification_version: 4
|
|
207
|
+
summary: Sockudo API client
|
|
208
|
+
test_files: []
|