ably-pubsub-core 0.0.1.pre

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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ably/agent.rb +8 -0
  3. data/lib/ably/auth.rb +791 -0
  4. data/lib/ably/exceptions.rb +169 -0
  5. data/lib/ably/logger.rb +119 -0
  6. data/lib/ably/models/auth_details.rb +45 -0
  7. data/lib/ably/models/channel_details.rb +69 -0
  8. data/lib/ably/models/channel_metrics.rb +107 -0
  9. data/lib/ably/models/channel_occupancy.rb +50 -0
  10. data/lib/ably/models/channel_options.rb +120 -0
  11. data/lib/ably/models/channel_state_change.rb +84 -0
  12. data/lib/ably/models/channel_status.rb +63 -0
  13. data/lib/ably/models/cipher_params.rb +137 -0
  14. data/lib/ably/models/connection_details.rb +149 -0
  15. data/lib/ably/models/connection_state_change.rb +83 -0
  16. data/lib/ably/models/delta_extras.rb +28 -0
  17. data/lib/ably/models/device_details.rb +126 -0
  18. data/lib/ably/models/device_push_details.rb +94 -0
  19. data/lib/ably/models/error_info.rb +104 -0
  20. data/lib/ably/models/http_paginated_response.rb +112 -0
  21. data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
  22. data/lib/ably/models/message.rb +223 -0
  23. data/lib/ably/models/message_encoders/base.rb +135 -0
  24. data/lib/ably/models/message_encoders/base64.rb +40 -0
  25. data/lib/ably/models/message_encoders/cipher.rb +84 -0
  26. data/lib/ably/models/message_encoders/json.rb +34 -0
  27. data/lib/ably/models/message_encoders/utf8.rb +26 -0
  28. data/lib/ably/models/nil_logger.rb +20 -0
  29. data/lib/ably/models/paginated_result.rb +201 -0
  30. data/lib/ably/models/presence_message.rb +244 -0
  31. data/lib/ably/models/protocol_message.rb +288 -0
  32. data/lib/ably/models/push_channel_subscription.rb +114 -0
  33. data/lib/ably/models/stats.rb +242 -0
  34. data/lib/ably/models/stats_types.rb +107 -0
  35. data/lib/ably/models/token_details.rb +148 -0
  36. data/lib/ably/models/token_request.rb +180 -0
  37. data/lib/ably/modules/ably.rb +26 -0
  38. data/lib/ably/modules/async_wrapper.rb +69 -0
  39. data/lib/ably/modules/channels_collection.rb +105 -0
  40. data/lib/ably/modules/conversions.rb +153 -0
  41. data/lib/ably/modules/encodeable.rb +111 -0
  42. data/lib/ably/modules/enum.rb +230 -0
  43. data/lib/ably/modules/event_emitter.rb +200 -0
  44. data/lib/ably/modules/event_machine_helpers.rb +26 -0
  45. data/lib/ably/modules/exception_codes.rb +128 -0
  46. data/lib/ably/modules/http_helpers.rb +41 -0
  47. data/lib/ably/modules/message_emitter.rb +78 -0
  48. data/lib/ably/modules/message_pack.rb +14 -0
  49. data/lib/ably/modules/model_common.rb +84 -0
  50. data/lib/ably/modules/safe_deferrable.rb +83 -0
  51. data/lib/ably/modules/safe_yield.rb +41 -0
  52. data/lib/ably/modules/state_emitter.rb +181 -0
  53. data/lib/ably/modules/state_machine.rb +59 -0
  54. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  55. data/lib/ably/modules/uses_state_machine.rb +111 -0
  56. data/lib/ably/realtime/auth.rb +283 -0
  57. data/lib/ably/realtime/channel/channel_manager.rb +272 -0
  58. data/lib/ably/realtime/channel/channel_properties.rb +40 -0
  59. data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
  60. data/lib/ably/realtime/channel/publisher.rb +82 -0
  61. data/lib/ably/realtime/channel/push_channel.rb +69 -0
  62. data/lib/ably/realtime/channel.rb +440 -0
  63. data/lib/ably/realtime/channels.rb +71 -0
  64. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
  65. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
  66. data/lib/ably/realtime/client.rb +363 -0
  67. data/lib/ably/realtime/connection/connection_manager.rb +594 -0
  68. data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
  69. data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
  70. data/lib/ably/realtime/connection.rb +738 -0
  71. data/lib/ably/realtime/models/nil_channel.rb +30 -0
  72. data/lib/ably/realtime/presence/members_map.rb +395 -0
  73. data/lib/ably/realtime/presence/presence_manager.rb +78 -0
  74. data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
  75. data/lib/ably/realtime/presence.rb +502 -0
  76. data/lib/ably/realtime/push/admin.rb +75 -0
  77. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  78. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  79. data/lib/ably/realtime/push.rb +25 -0
  80. data/lib/ably/realtime/recovery_key_context.rb +36 -0
  81. data/lib/ably/realtime.rb +60 -0
  82. data/lib/ably/rest/channel/push_channel.rb +59 -0
  83. data/lib/ably/rest/channel.rb +194 -0
  84. data/lib/ably/rest/channels.rb +47 -0
  85. data/lib/ably/rest/client.rb +727 -0
  86. data/lib/ably/rest/middleware/encoder.rb +49 -0
  87. data/lib/ably/rest/middleware/exceptions.rb +53 -0
  88. data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  89. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
  90. data/lib/ably/rest/middleware/logger.rb +58 -0
  91. data/lib/ably/rest/middleware/parse_json.rb +27 -0
  92. data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
  93. data/lib/ably/rest/presence.rb +113 -0
  94. data/lib/ably/rest/push/admin.rb +58 -0
  95. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  96. data/lib/ably/rest/push/device_registrations.rb +114 -0
  97. data/lib/ably/rest/push.rb +25 -0
  98. data/lib/ably/rest.rb +40 -0
  99. data/lib/ably/util/ably_extensions.rb +29 -0
  100. data/lib/ably/util/crypto.rb +134 -0
  101. data/lib/ably/util/pub_sub.rb +45 -0
  102. data/lib/ably/util/safe_deferrable.rb +44 -0
  103. data/lib/ably/version.rb +7 -0
  104. data/lib/ably.rb +16 -0
  105. metadata +497 -0
@@ -0,0 +1,114 @@
1
+ module Ably::Rest
2
+ class Push
3
+ # Manage device registrations for push notifications
4
+ class DeviceRegistrations
5
+ include Ably::Modules::Conversions
6
+
7
+ # @api private
8
+ attr_reader :client
9
+
10
+ # @api private
11
+ attr_reader :admin
12
+
13
+ def initialize(admin)
14
+ @admin = admin
15
+ @client = admin.client
16
+ end
17
+
18
+ # Get registered device by device ID
19
+ #
20
+ # @spec RSH1b1
21
+ #
22
+ # @param [String, Ably::Models::DeviceDetails] device_id the device to retrieve
23
+ #
24
+ # @return [Ably::Models::DeviceDetails] Returns {Ably::Models::DeviceDetails} if a match is found else a {Ably::Exceptions::ResourceMissing} is raised
25
+ #
26
+ def get(device_id)
27
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
28
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
29
+
30
+ DeviceDetails(client.get("/push/deviceRegistrations/#{device_id}").body)
31
+ end
32
+
33
+ # List registered devices filtered by optional params
34
+ #
35
+ # @spec RSH1b2
36
+ #
37
+ # @param [Hash] params the filter options for the list registered device request
38
+ # @option params [String] :client_id filter by devices registered to a client identifier. Cannot be used with +device_id+ param
39
+ # @option params [String] :device_id filter by unique device ID. Cannot be used with +client_id+ param
40
+ # @option params [Integer] :limit maximum number of devices to retrieve up to 1,000, defaults to 100
41
+ #
42
+ # @return [Ably::Models::PaginatedResult<Ably::Models::DeviceDetails>] Paginated list of matching {Ably::Models::DeviceDetails}
43
+ #
44
+ def list(params = {})
45
+ params = {} if params.nil?
46
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
47
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
48
+
49
+ params = params.clone
50
+
51
+ paginated_options = {
52
+ coerce_into: 'Ably::Models::DeviceDetails',
53
+ async_blocking_operations: params.delete(:async_blocking_operations),
54
+ }
55
+
56
+ response = client.get('/push/deviceRegistrations', IdiomaticRubyWrapper(params).as_json)
57
+
58
+ Ably::Models::PaginatedResult.new(response, '', client, paginated_options)
59
+ end
60
+
61
+ # Registers or updates a {Ably::Models::DeviceDetails} object with Ably.
62
+ # Returns the new, or updated {Ably::Models::DeviceDetails} object.
63
+ #
64
+ # @spec RSH1b3
65
+ #
66
+ # @param [Ably::Models::DeviceDetails, Hash] device the device details to save
67
+ #
68
+ # @return [void]
69
+ #
70
+ def save(device)
71
+ device_details = DeviceDetails(device)
72
+ raise ArgumentError, "Device ID is required yet is empty" if device_details.id.nil? || device_details == ''
73
+
74
+ client.put("/push/deviceRegistrations/#{device_details.id}", device_details.as_json)
75
+ end
76
+
77
+ # Remove device
78
+ #
79
+ # @spec RSH1b4
80
+ #
81
+ # @param [String, Ably::Models::DeviceDetails] device_id the device to remove
82
+ #
83
+ # @return [void]
84
+ #
85
+ def remove(device_id)
86
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
87
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
88
+
89
+ client.delete("/push/deviceRegistrations/#{device_id}", {})
90
+ end
91
+
92
+ # Removes all devices registered to receive push notifications from Ably matching the filter params provided.
93
+ #
94
+ # @spec RSH1b5
95
+ #
96
+ # @param [Hash] params the filter params for the remove request
97
+ # @option params [String] :client_id remove devices registered to a client identifier. Cannot be used with +device_id+ param
98
+ # @option params [String] :device_id remove device with this unique device ID. Cannot be used with +client_id+ param
99
+ #
100
+ # @return [void]
101
+ #
102
+ def remove_where(params = {})
103
+ filter = if params.kind_of?(Ably::Models::DeviceDetails)
104
+ { 'deviceId' => params.id }
105
+ else
106
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
107
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
108
+ IdiomaticRubyWrapper(params).as_json
109
+ end
110
+ client.delete("/push/deviceRegistrations", filter)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,25 @@
1
+ require 'ably/rest/push/admin'
2
+
3
+ module Ably
4
+ module Rest
5
+ # Class providing push notification functionality
6
+ class Push
7
+ include Ably::Modules::Conversions
8
+
9
+ # @private
10
+ attr_reader :client
11
+
12
+ def initialize(client)
13
+ @client = client
14
+ end
15
+
16
+ # Admin features for push notifications like managing devices and channel subscriptions
17
+ #
18
+ # @return [Ably::Rest::Push::Admin]
19
+ #
20
+ def admin
21
+ @admin ||= Admin.new(self)
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/ably/rest.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'ably/rest/channel'
2
+ require 'ably/rest/channels'
3
+ require 'ably/rest/client'
4
+ require 'ably/rest/push'
5
+ require 'ably/rest/presence'
6
+
7
+ require 'ably/models/message_encoders/base'
8
+
9
+ Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
10
+ require file
11
+ end
12
+
13
+ module Ably
14
+ # Rest provides the top-level class to be instanced for the Ably Rest library
15
+ #
16
+ # @example
17
+ # client = Ably::Rest.new("xxxxx")
18
+ # channel = client.channel("test")
19
+ # channel.publish "greeting", "data"
20
+ #
21
+ module Rest
22
+ # Convenience method providing an alias to {Ably::Rest::Client} constructor.
23
+ #
24
+ # @param (see Ably::Rest::Client#initialize)
25
+ # @option options (see Ably::Rest::Client#initialize)
26
+ #
27
+ # @return [Ably::Rest::Client]
28
+ #
29
+ # @example
30
+ # # create a new client authenticating with basic auth
31
+ # client = Ably::Rest.new('key.id:secret')
32
+ #
33
+ # # create a new client authenticating with basic auth and a client_id
34
+ # client = Ably::Rest.new(key: 'key.id:secret', client_id: 'john')
35
+ #
36
+ def self.new(options)
37
+ Ably::Rest::Client.new(options)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ably::Util
4
+ module AblyExtensions
5
+ refine Object do
6
+ def nil_or_empty?
7
+ self.nil? || self.empty?
8
+ end
9
+ end
10
+
11
+ refine Hash do
12
+ def fetch_with_default(key, default)
13
+ value = self.fetch(key, default)
14
+ if value.nil?
15
+ return default
16
+ end
17
+ return value
18
+ end
19
+
20
+ def delete_with_default(key, default)
21
+ value = self.delete(key)
22
+ if value.nil?
23
+ return default
24
+ end
25
+ return value
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,134 @@
1
+ require 'msgpack'
2
+ require 'openssl'
3
+
4
+ module Ably::Util
5
+ # Contains the properties required to configure the encryption of {Ably::Models::Message} payloads.
6
+ #
7
+ class Crypto
8
+ DEFAULTS = {
9
+ algorithm: 'aes',
10
+ mode: 'cbc',
11
+ key_length: 256,
12
+ }
13
+
14
+ BLOCK_LENGTH = 16
15
+
16
+ # Configured {Ably::Models::CipherParams} for this Crypto object, see {#initialize} for a list of configureable options
17
+ #
18
+ # @return [Ably::Models::CipherParams]
19
+ #
20
+ attr_reader :cipher_params
21
+
22
+ # Creates a {Ably::Util::Crypto} object
23
+ #
24
+ # @param [Hash] params a Hash used to configure the Crypto library's {Ably::Models::CipherParams}
25
+ # @option params (see Ably::Models::CipherParams#initialize)
26
+ #
27
+ # @return [Ably::Util::Crypto]
28
+ #
29
+ # @example
30
+ # key = Ably::Util::Crypto.generate_random_key
31
+ # crypto = Ably::Util::Crypto.new(key: key)
32
+ # encrypted = crypto.encrypt('secret text')
33
+ # crypto.decrypt(decrypted) # => 'secret text'
34
+ #
35
+ def initialize(params)
36
+ @fixed_iv = params[:fixed_iv]
37
+ @cipher_params = Ably::Models::CipherParams(params)
38
+ end
39
+
40
+ # Returns a {Ably::Models::CipherParams} object, using the default values for any fields not supplied by the `Hash` object.
41
+ #
42
+ # @spec RSE1, RSE1b, RSE1b
43
+ #
44
+ # @param [Hash] params a Hash used to configure the Crypto library's {Ably::Models::CipherParams}
45
+ # @option params (see {Ably::Models::CipherParams#initialize})
46
+ #
47
+ # @return [Ably::Models::CipherParams] Configured cipher params with :key, :algorithm, :mode, :key_length attributes
48
+ #
49
+ def self.get_default_params(params = {})
50
+ Ably::Models::CipherParams(params)
51
+ end
52
+
53
+ # Generates a random key to be used in the encryption of the channel. If the language cryptographic randomness
54
+ # primitives are blocking or async, a callback is used. The callback returns a generated binary key.
55
+ #
56
+ # @spec RSE2, RSE2a, RSE2b
57
+ #
58
+ # @param [Integer] key_length The length of the key, in bits, to be generated. If not specified, this is equal to the default keyLength of the default algorithm: for AES this is 256 bits.
59
+ # @return Binary The key as a binary, for example, a byte array.
60
+ #
61
+ def self.generate_random_key(key_length = DEFAULTS.fetch(:key_length))
62
+ params = DEFAULTS.merge(key_length: key_length)
63
+ OpenSSL::Cipher.new(cipher_type(params)).random_key
64
+ end
65
+
66
+ # The Cipher algorithm string such as AES-128-CBC
67
+ # @api private
68
+ def self.cipher_type(options)
69
+ Ably::Models::CipherParams.cipher_type(options)
70
+ end
71
+
72
+ # Encrypt payload using configured Cipher
73
+ #
74
+ # @param [String] payload the payload to be encrypted
75
+ # @param [Hash] encrypt_options an options Hash to configure the encrypt action
76
+ # @option encrypt_options [String] :iv optionally use the provided Initialization Vector instead of a randomly generated IV
77
+ #
78
+ # @return [String] binary string with +Encoding::ASCII_8BIT+ encoding
79
+ #
80
+ def encrypt(payload, encrypt_options = {})
81
+ cipher = openssl_cipher
82
+ cipher.encrypt
83
+ cipher.key = key
84
+ iv = encrypt_options[:iv] || fixed_iv || cipher.random_iv
85
+ cipher.iv = iv
86
+ iv << cipher.update(payload) unless payload.empty?
87
+ iv << cipher.final
88
+ end
89
+
90
+ # Decrypt payload using configured Cipher
91
+ #
92
+ # @param [String] encrypted_payload_with_iv the encrypted payload to be decrypted
93
+ #
94
+ # @return [String]
95
+ #
96
+ def decrypt(encrypted_payload_with_iv)
97
+ raise Ably::Exceptions::CipherError, 'iv is missing or not long enough' unless encrypted_payload_with_iv.length >= BLOCK_LENGTH*2
98
+
99
+ iv = encrypted_payload_with_iv.slice(0...BLOCK_LENGTH)
100
+ encrypted_payload = encrypted_payload_with_iv.slice(BLOCK_LENGTH..-1)
101
+
102
+ decipher = openssl_cipher
103
+ decipher.decrypt
104
+ decipher.key = key
105
+ decipher.iv = iv
106
+
107
+ decipher.update(encrypted_payload) << decipher.final
108
+ end
109
+
110
+ # Generate a random IV
111
+ # @return [String]
112
+ def random_iv
113
+ openssl_cipher.random_iv
114
+ end
115
+
116
+ private
117
+ # Used solely for tests to fix the IV instead of randomly generate one
118
+ attr_reader :fixed_iv
119
+
120
+ # Generate a random key
121
+ # @return [String]
122
+ def random_key
123
+ openssl_cipher.random_key
124
+ end
125
+
126
+ def key
127
+ cipher_params.key
128
+ end
129
+
130
+ def openssl_cipher
131
+ @openssl_cipher ||= OpenSSL::Cipher.new(cipher_params.cipher_type)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,45 @@
1
+ require 'ably/modules/event_emitter.rb'
2
+
3
+ module Ably::Util
4
+ # PubSub class provides methods to publish & subscribe to events, with methods and naming
5
+ # intentionally different to EventEmitter as it is intended for private message handling
6
+ # within the client library.
7
+ #
8
+ # @example
9
+ # class Channel
10
+ # def messages
11
+ # @messages ||= PubSub.new
12
+ # end
13
+ # end
14
+ #
15
+ # channel = Channel.new
16
+ # channel.messages.subscribe(:event) { |name| puts "Event message #{name} received" }
17
+ # channel.messages.publish :event, "Test"
18
+ # #=> "Event message Test received"
19
+ # channel.messages.remove :event
20
+ #
21
+ class PubSub
22
+ include Ably::Modules::EventEmitter
23
+
24
+ # Ensure new PubSub object does not share class instance variables
25
+ def self.new(options = {})
26
+ Class.new(PubSub).allocate.tap do |pub_sub_object|
27
+ pub_sub_object.send(:initialize, options)
28
+ end
29
+ end
30
+
31
+ def inspect
32
+ "<#PubSub: @event_emitter_coerce_proc: #{self.class.event_emitter_coerce_proc.inspect}\n @callbacks: #{callbacks}>"
33
+ end
34
+
35
+ def initialize(options = {})
36
+ self.class.instance_eval do
37
+ configure_event_emitter options
38
+
39
+ alias_method :subscribe, :unsafe_on
40
+ alias_method :publish, :emit
41
+ alias_method :unsubscribe, :unsafe_off
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ module Ably::Util
2
+ # SafeDeferrable class provides a Deferrable that is safe to use for for public interfaces
3
+ # of this client library. Any exceptions raised in the success or failure callbacks are
4
+ # caught and logged to the provided logger.
5
+ #
6
+ # An exception in a callback provided by a developer should not break this client library
7
+ # and stop further execution of code.
8
+ #
9
+ class SafeDeferrable
10
+ include Ably::Modules::SafeDeferrable
11
+
12
+ attr_reader :logger
13
+
14
+ def initialize(logger)
15
+ @logger = logger
16
+ end
17
+
18
+ # Create a new {SafeDeferrable} and fail immediately with the provided error in the next eventloop cycle
19
+ #
20
+ # @param error [Ably::Exceptions::BaseAblyException, Ably::Models::ErrorInfo] The error used to fail the newly created {SafeDeferrable}
21
+ #
22
+ # @return [SafeDeferrable]
23
+ #
24
+ def self.new_and_fail_immediately(logger, error)
25
+ new(logger).tap do |deferrable|
26
+ EventMachine.next_tick do
27
+ deferrable.fail error
28
+ end
29
+ end
30
+ end
31
+
32
+ # Create a new {SafeDeferrable} and succeed immediately with the provided arguments in the next eventloop cycle
33
+ #
34
+ # @return [SafeDeferrable]
35
+ #
36
+ def self.new_and_succeed_immediately(logger, *args)
37
+ new(logger).tap do |deferrable|
38
+ EventMachine.next_tick do
39
+ deferrable.succeed *args
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ module Ably
2
+ VERSION = '0.0.1.pre'
3
+ # The level of compatibility with the Ably service that this SDK supports.
4
+ # Also referred to as the 'wire protocol version'.
5
+ # spec : CSV2
6
+ PROTOCOL_VERSION = '2'
7
+ end
data/lib/ably.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'addressable/uri'
2
+
3
+ require 'ably/version'
4
+ require 'ably/agent'
5
+
6
+ %w(modules util).each do |namespace|
7
+ Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).sort.each do |file|
8
+ require file
9
+ end
10
+ end
11
+
12
+ require 'ably/auth'
13
+ require 'ably/exceptions'
14
+ require 'ably/logger'
15
+ require 'ably/realtime'
16
+ require 'ably/rest'