sockudo 1.0.0 → 2.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.
@@ -1,27 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pusher-signature'
2
4
  require 'digest/md5'
3
5
  require 'multi_json'
6
+ require 'openssl'
4
7
 
5
8
  module Sockudo
6
9
  class Request
7
10
  attr_reader :body, :params
8
11
 
9
12
  def initialize(client, verb, uri, params, body = nil, extra_headers = {})
10
- @client, @verb, @uri = client, verb, uri
13
+ @client = client
14
+ @verb = verb
15
+ @uri = uri
11
16
  @head = {
12
- 'X-Pusher-Library' => 'sockudo-http-ruby ' + Sockudo::VERSION
17
+ 'X-Pusher-Library' => "sockudo-http-ruby #{Sockudo::VERSION}"
13
18
  }
14
19
  @head.merge!(extra_headers) if extra_headers && !extra_headers.empty?
15
20
 
16
21
  @body = body
22
+ params = params.each_with_object({}) { |(key, value), result| result[key.to_s] = value }
17
23
  if body
18
- params[:body_md5] = Digest::MD5.hexdigest(body)
24
+ params['body_md5'] = Digest::MD5.hexdigest(body)
19
25
  @head['Content-Type'] = 'application/json'
20
26
  end
21
27
 
22
- request = Sockudo::Signature::Request.new(verb.to_s.upcase, uri.path, params)
23
- request.sign(client.authentication_token)
24
- @params = request.signed_params
28
+ @params = signed_params(params)
25
29
  end
26
30
 
27
31
  def send_sync
@@ -36,9 +40,9 @@ module Sockudo
36
40
  raise error
37
41
  end
38
42
 
39
- body = response.body ? response.body.chomp : nil
43
+ body = response.body&.chomp
40
44
 
41
- return handle_response(response.code.to_i, body)
45
+ handle_response(response.code.to_i, body)
42
46
  end
43
47
 
44
48
  def send_async
@@ -47,46 +51,65 @@ module Sockudo
47
51
  df = EM::DefaultDeferrable.new
48
52
 
49
53
  http = case @verb
50
- when :post
51
- http_client.post({
52
- :query => @params, :body => @body, :head => @head
53
- })
54
- when :get
55
- http_client.get({
56
- :query => @params, :head => @head
57
- })
58
- else
59
- raise "Unsupported verb"
54
+ when :post
55
+ http_client.post({
56
+ query: @params, body: @body, head: @head
57
+ })
58
+ when :get
59
+ http_client.get({
60
+ query: @params, head: @head
61
+ })
62
+ when :delete
63
+ http_client.delete({
64
+ query: @params, head: @head
65
+ })
66
+ else
67
+ raise 'Unsupported verb'
68
+ end
69
+ http.callback do
70
+ df.succeed(handle_response(http.response_header.status, http.response.chomp))
71
+ rescue StandardError => e
72
+ df.fail(e)
60
73
  end
61
- http.callback {
62
- begin
63
- df.succeed(handle_response(http.response_header.status, http.response.chomp))
64
- rescue => e
65
- df.fail(e)
66
- end
67
- }
68
- http.errback { |e|
74
+ http.errback do |_e|
69
75
  message = "Network error connecting to sockudo (#{http.error})"
70
76
  Sockudo.logger.debug(message)
71
77
  df.fail(Error.new(message))
72
- }
78
+ end
73
79
 
74
- return df
80
+ df
75
81
  else
76
82
  http = @client.sync_http_client
77
83
 
78
- return http.request_async(@verb, @uri, @params, @body, @head)
84
+ http.request_async(@verb, @uri, @params, @body, @head)
79
85
  end
80
86
  end
81
87
 
82
88
  private
83
89
 
90
+ def signed_params(params)
91
+ @client.authentication_token
92
+ params = params.merge({
93
+ 'auth_key' => @client.key,
94
+ 'auth_timestamp' => Time.now.to_i.to_s,
95
+ 'auth_version' => '1.0'
96
+ })
97
+ params_for_signing = params.each_with_object({}) do |(key, value), result|
98
+ result[key.downcase] = value
99
+ end
100
+ canonical_query = params_for_signing.sort.map { |key, value| "#{key}=#{value}" }.join('&')
101
+ string_to_sign = [@verb.to_s.upcase, @uri.path, canonical_query].join("\n")
102
+ params.merge('auth_signature' => OpenSSL::HMAC.hexdigest('sha256', @client.secret, string_to_sign))
103
+ end
104
+
84
105
  def handle_response(status_code, body)
85
106
  case status_code
86
- when 200
87
- return symbolize_first_level(MultiJson.decode(body))
107
+ when 200, 201
108
+ symbolize_first_level(MultiJson.decode(body))
88
109
  when 202
89
- return body.empty? ? true : symbolize_first_level(MultiJson.decode(body))
110
+ body.empty? || symbolize_first_level(MultiJson.decode(body))
111
+ when 204
112
+ {}
90
113
  when 400
91
114
  raise Error, "Bad request: #{body}"
92
115
  when 401
@@ -94,18 +117,30 @@ module Sockudo
94
117
  when 404
95
118
  raise Error, "404 Not found (#{@uri.path})"
96
119
  when 407
97
- raise Error, "Proxy Authentication Required"
120
+ raise Error, 'Proxy Authentication Required'
98
121
  when 413
99
- raise Error, "Payload Too Large > 10KB"
122
+ raise Error, 'Payload Too Large > 10KB'
100
123
  else
101
124
  raise Error, "Unknown error (status code #{status_code}): #{body}"
102
125
  end
103
126
  end
104
127
 
105
128
  def symbolize_first_level(hash)
106
- hash.inject({}) do |result, (key, value)|
107
- result[key.to_sym] = value
108
- result
129
+ hash.each_with_object({}) do |(key, value), result|
130
+ result[key.to_sym] = deep_symbolize(value)
131
+ end
132
+ end
133
+
134
+ def deep_symbolize(value)
135
+ case value
136
+ when Hash
137
+ value.each_with_object({}) do |(key, nested_value), result|
138
+ result[key.to_sym] = deep_symbolize(nested_value)
139
+ end
140
+ when Array
141
+ value.map { |item| deep_symbolize(item) }
142
+ else
143
+ value
109
144
  end
110
145
  end
111
146
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sockudo
2
4
  class Resource
3
5
  def initialize(client, path)
@@ -5,12 +7,12 @@ module Sockudo
5
7
  @path = path
6
8
  end
7
9
 
8
- def get(params)
9
- create_request(:get, params).send_sync
10
+ def get(params, headers = {})
11
+ create_request(:get, params, nil, headers).send_sync
10
12
  end
11
13
 
12
- def get_async(params)
13
- create_request(:get, params).send_async
14
+ def get_async(params, headers = {})
15
+ create_request(:get, params, nil, headers).send_async
14
16
  end
15
17
 
16
18
  def post(params, headers = {})
@@ -23,6 +25,10 @@ module Sockudo
23
25
  create_request(:post, {}, body, headers).send_async
24
26
  end
25
27
 
28
+ def delete(params, headers = {})
29
+ create_request(:delete, params, nil, headers).send_sync
30
+ end
31
+
26
32
  private
27
33
 
28
34
  def create_request(verb, params, body = nil, extra_headers = {})
@@ -30,7 +36,7 @@ module Sockudo
30
36
  end
31
37
 
32
38
  def url
33
- @_url ||= @client.url(@path)
39
+ @url ||= @client.url(@path)
34
40
  end
35
41
  end
36
42
  end
data/lib/sockudo/utils.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sockudo
2
4
  module Utils
3
5
  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
6
+ return if socket_id && /\A\d+\.\d+\z/.match(socket_id)
7
+
8
+ raise Sockudo::Error, "Invalid socket ID #{socket_id.inspect}"
7
9
  end
8
10
 
9
11
  # Compute authentication string required as part of the user authentication
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sockudo
2
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'multi_json'
2
4
  require 'openssl'
3
5
 
@@ -33,7 +35,7 @@ module Sockudo
33
35
  # For Rack::Request and ActionDispatch::Request
34
36
  if request.respond_to?(:env) && request.respond_to?(:content_type)
35
37
  @key = request.env['HTTP_X_PUSHER_KEY']
36
- @signature = request.env["HTTP_X_PUSHER_SIGNATURE"]
38
+ @signature = request.env['HTTP_X_PUSHER_SIGNATURE']
37
39
  @content_type = request.content_type
38
40
 
39
41
  request.body.rewind
@@ -49,25 +51,27 @@ module Sockudo
49
51
  # matches the configured key & secret. In the case that the webhook is
50
52
  # invalid, the reason is logged
51
53
  #
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.
54
+ # @param extra_tokens [Hash] If you have extra tokens for your Sockudo app, you can specify them
55
+ # so that they're used to attempt validation.
53
56
  #
54
57
  def valid?(extra_tokens = nil)
55
- extra_tokens = [extra_tokens] if extra_tokens.kind_of?(Hash)
58
+ extra_tokens = [extra_tokens] if extra_tokens.is_a?(Hash)
56
59
  if @key == @client.key
57
- return check_signature(@client.secret)
60
+ return signature_valid?(@client.secret)
58
61
  elsif extra_tokens
59
62
  extra_tokens.each do |token|
60
- return check_signature(token[:secret]) if @key == token[:key]
63
+ return signature_valid?(token[:secret]) if @key == token[:key]
61
64
  end
62
65
  end
66
+
63
67
  Sockudo.logger.warn "Received webhook with unknown key: #{key}"
64
- return false
68
+ false
65
69
  end
66
70
 
67
71
  # Array of events (as Hashes) contained inside the webhook
68
72
  #
69
73
  def events
70
- data["events"]
74
+ data['events']
71
75
  end
72
76
 
73
77
  # The time at which the WebHook was initially triggered by Sockudo, i.e.
@@ -76,35 +80,31 @@ module Sockudo
76
80
  # @return [Time]
77
81
  #
78
82
  def time
79
- Time.at(data["time_ms"].to_f/1000)
83
+ Time.at(data['time_ms'].to_f / 1000)
80
84
  end
81
85
 
82
86
  # Access the parsed WebHook body
83
87
  #
84
88
  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
89
+ @data ||= case @content_type
90
+ when 'application/json'
91
+ MultiJson.decode(@body)
92
+ else
93
+ raise "Unknown Content-Type (#{@content_type})"
94
+ end
93
95
  end
94
96
 
95
97
  private
96
98
 
97
99
  # Checks signature against secret and returns boolean
98
100
  #
99
- def check_signature(secret)
100
- digest = OpenSSL::Digest::SHA256.new
101
+ def signature_valid?(secret)
102
+ digest = OpenSSL::Digest.new('SHA256')
101
103
  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
104
+ return true if @signature == expected
105
+
106
+ Sockudo.logger.warn "Received WebHook with invalid signature: got #{@signature}, expected #{expected}"
107
+ false
108
108
  end
109
109
  end
110
110
  end
data/lib/sockudo.rb CHANGED
@@ -1,7 +1,10 @@
1
- autoload 'Logger', 'logger'
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
2
4
  require 'securerandom'
3
5
  require 'uri'
4
6
  require 'forwardable'
7
+ require 'pusher-signature'
5
8
 
6
9
  require 'sockudo/utils'
7
10
  require 'sockudo/client'
@@ -19,11 +22,13 @@ module Sockudo
19
22
  # end
20
23
  class Error < RuntimeError; end
21
24
  class AuthenticationError < Error; end
25
+
22
26
  class ConfigurationError < Error
23
27
  def initialize(key)
24
- super "missing key `#{key}' in the client configuration"
28
+ super("missing key `#{key}' in the client configuration")
25
29
  end
26
30
  end
31
+
27
32
  class HTTPError < Error; attr_accessor :original_error; end
28
33
 
29
34
  class << self
@@ -36,10 +41,21 @@ module Sockudo
36
41
 
37
42
  def_delegators :default_client, :authentication_token, :url, :cluster
38
43
  def_delegators :default_client, :encrypted=, :url=, :cluster=
39
- def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
44
+ def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=,
45
+ :keep_alive_timeout=
40
46
 
41
47
  def_delegators :default_client, :get, :get_async, :post, :post_async
42
- def_delegators :default_client, :channels, :channel_info, :channel_users
48
+ def_delegators :default_client, :channels, :channel_info, :channel_history, :channel_users
49
+ def_delegators :default_client, :get_message, :get_message_versions, :update_message,
50
+ :delete_message, :append_message
51
+ def_delegators :default_client, :activate_device, :create_device_activation, :update_device_registration,
52
+ :list_device_registrations, :get_device_registration, :delete_device_registration,
53
+ :remove_device_registrations_by_client, :upsert_channel_push_subscription,
54
+ :list_channel_push_subscriptions, :delete_channel_push_subscriptions,
55
+ :list_channel_push_subscription_channels, :list_push_credentials,
56
+ :put_push_credential, :publish_push, :publish_push_direct, :publish_push_batch,
57
+ :schedule_push, :get_publish_status, :cancel_scheduled_push,
58
+ :post_push_delivery_status
43
59
  def_delegators :default_client, :trigger, :trigger_batch, :trigger_async, :trigger_batch_async
44
60
  def_delegators :default_client, :authenticate, :webhook, :channel, :[]
45
61
  def_delegators :default_client, :notify
metadata CHANGED
@@ -1,85 +1,84 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sockudo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sockudo
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-04-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: multi_json
13
+ name: httpclient
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1.15'
18
+ version: '2.8'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '1.15'
25
+ version: '2.8'
27
26
  - !ruby/object:Gem::Dependency
28
- name: pusher-signature
27
+ name: logger
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 0.1.8
32
+ version: '1.7'
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: 0.1.8
39
+ version: '1.7'
41
40
  - !ruby/object:Gem::Dependency
42
- name: httpclient
41
+ name: multi_json
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '2.8'
46
+ version: '1.15'
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '2.8'
53
+ version: '1.15'
55
54
  - !ruby/object:Gem::Dependency
56
- name: rspec
55
+ name: pusher-signature
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - "~>"
60
59
  - !ruby/object:Gem::Version
61
- version: '3.9'
62
- type: :development
60
+ version: 0.1.8
61
+ type: :runtime
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - "~>"
67
66
  - !ruby/object:Gem::Version
68
- version: '3.9'
67
+ version: 0.1.8
69
68
  - !ruby/object:Gem::Dependency
70
- name: webmock
69
+ name: addressable
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
72
  - - "~>"
74
73
  - !ruby/object:Gem::Version
75
- version: '3.9'
74
+ version: '2.7'
76
75
  type: :development
77
76
  prerelease: false
78
77
  version_requirements: !ruby/object:Gem::Requirement
79
78
  requirements:
80
79
  - - "~>"
81
80
  - !ruby/object:Gem::Version
82
- version: '3.9'
81
+ version: '2.7'
83
82
  - !ruby/object:Gem::Dependency
84
83
  name: em-http-request
85
84
  requirement: !ruby/object:Gem::Requirement
@@ -95,19 +94,33 @@ dependencies:
95
94
  - !ruby/object:Gem::Version
96
95
  version: '1.1'
97
96
  - !ruby/object:Gem::Dependency
98
- name: addressable
97
+ name: json
99
98
  requirement: !ruby/object:Gem::Requirement
100
99
  requirements:
101
100
  - - "~>"
102
101
  - !ruby/object:Gem::Version
103
- version: '2.7'
102
+ version: '2.3'
104
103
  type: :development
105
104
  prerelease: false
106
105
  version_requirements: !ruby/object:Gem::Requirement
107
106
  requirements:
108
107
  - - "~>"
109
108
  - !ruby/object:Gem::Version
110
- version: '2.7'
109
+ version: '2.3'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.2'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.2'
111
124
  - !ruby/object:Gem::Dependency
112
125
  name: rake
113
126
  requirement: !ruby/object:Gem::Requirement
@@ -123,47 +136,61 @@ dependencies:
123
136
  - !ruby/object:Gem::Version
124
137
  version: '13.0'
125
138
  - !ruby/object:Gem::Dependency
126
- name: rack
139
+ name: rbnacl
127
140
  requirement: !ruby/object:Gem::Requirement
128
141
  requirements:
129
142
  - - "~>"
130
143
  - !ruby/object:Gem::Version
131
- version: '2.2'
144
+ version: '7.1'
132
145
  type: :development
133
146
  prerelease: false
134
147
  version_requirements: !ruby/object:Gem::Requirement
135
148
  requirements:
136
149
  - - "~>"
137
150
  - !ruby/object:Gem::Version
138
- version: '2.2'
151
+ version: '7.1'
139
152
  - !ruby/object:Gem::Dependency
140
- name: json
153
+ name: rspec
141
154
  requirement: !ruby/object:Gem::Requirement
142
155
  requirements:
143
156
  - - "~>"
144
157
  - !ruby/object:Gem::Version
145
- version: '2.3'
158
+ version: '3.9'
146
159
  type: :development
147
160
  prerelease: false
148
161
  version_requirements: !ruby/object:Gem::Requirement
149
162
  requirements:
150
163
  - - "~>"
151
164
  - !ruby/object:Gem::Version
152
- version: '2.3'
165
+ version: '3.9'
153
166
  - !ruby/object:Gem::Dependency
154
- name: rbnacl
167
+ name: rubocop
155
168
  requirement: !ruby/object:Gem::Requirement
156
169
  requirements:
157
170
  - - "~>"
158
171
  - !ruby/object:Gem::Version
159
- version: '7.1'
172
+ version: '1.80'
160
173
  type: :development
161
174
  prerelease: false
162
175
  version_requirements: !ruby/object:Gem::Requirement
163
176
  requirements:
164
177
  - - "~>"
165
178
  - !ruby/object:Gem::Version
166
- version: '7.1'
179
+ version: '1.80'
180
+ - !ruby/object:Gem::Dependency
181
+ name: webmock
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '3.9'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '3.9'
167
194
  description: Wrapper for Sockudo REST API
168
195
  email:
169
196
  - support@sockudo.com
@@ -185,8 +212,8 @@ files:
185
212
  homepage: http://github.com/sockudo/sockudo-http-ruby
186
213
  licenses:
187
214
  - MIT
188
- metadata: {}
189
- post_install_message:
215
+ metadata:
216
+ rubygems_mfa_required: 'true'
190
217
  rdoc_options: []
191
218
  require_paths:
192
219
  - lib
@@ -201,8 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
228
  - !ruby/object:Gem::Version
202
229
  version: '0'
203
230
  requirements: []
204
- rubygems_version: 3.4.20
205
- signing_key:
231
+ rubygems_version: 3.6.9
206
232
  specification_version: 4
207
233
  summary: Sockudo API client
208
234
  test_files: []