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.
- checksums.yaml +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- metadata +497 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require 'ably/models/message_encoders/base'
|
|
2
|
+
|
|
3
|
+
module Ably::Models
|
|
4
|
+
# Convert messsage argument to a {Message} object and associate with a protocol message if provided
|
|
5
|
+
#
|
|
6
|
+
# @param message [Message,Hash] A message object or Hash of message properties
|
|
7
|
+
# @param [Hash] options (see Message#initialize)
|
|
8
|
+
#
|
|
9
|
+
# @return [Message]
|
|
10
|
+
#
|
|
11
|
+
def self.Message(message, options = {})
|
|
12
|
+
case message
|
|
13
|
+
when Message
|
|
14
|
+
message.tap do
|
|
15
|
+
message.assign_to_protocol_message options[:protocol_message] if options[:protocol_message]
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
Message.new(message, options)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Contains an individual message that is sent to, or received from, Ably.
|
|
23
|
+
#
|
|
24
|
+
class Message
|
|
25
|
+
include Ably::Modules::Conversions
|
|
26
|
+
include Ably::Modules::Encodeable
|
|
27
|
+
include Ably::Modules::ModelCommon
|
|
28
|
+
include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
|
|
29
|
+
|
|
30
|
+
# Statically register a default set of encoders for this class
|
|
31
|
+
Ably::Models::MessageEncoders.register_default_encoders self
|
|
32
|
+
|
|
33
|
+
# {Message} initializer
|
|
34
|
+
#
|
|
35
|
+
# @spec TM2, TM3
|
|
36
|
+
#
|
|
37
|
+
# @param attributes [Hash] object with the underlying message detail key value attributes
|
|
38
|
+
# @param [Hash] options an options Hash for this initializer
|
|
39
|
+
# @option options [ProtocolMessage] :protocol_message An optional protocol message to assocate the presence message with
|
|
40
|
+
# @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
|
|
41
|
+
#
|
|
42
|
+
def initialize(attributes, options = {})
|
|
43
|
+
@logger = options[:logger] # Logger expected for SafeDeferrable
|
|
44
|
+
@protocol_message = options[:protocol_message]
|
|
45
|
+
@raw_hash_object = attributes
|
|
46
|
+
|
|
47
|
+
set_attributes_object attributes
|
|
48
|
+
|
|
49
|
+
self.attributes[:name] = ensure_utf_8(:name, name, allow_nil: true) if name
|
|
50
|
+
self.attributes[:client_id] = ensure_utf_8(:client_id, client_id, allow_nil: true) if client_id
|
|
51
|
+
self.attributes[:encoding] = ensure_utf_8(:encoding, encoding, allow_nil: true) if encoding
|
|
52
|
+
|
|
53
|
+
self.attributes.freeze
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The client ID of the publisher of this message.
|
|
57
|
+
#
|
|
58
|
+
# @spec RSL1g1, TM2b
|
|
59
|
+
#
|
|
60
|
+
# @return [String]
|
|
61
|
+
#
|
|
62
|
+
def client_id
|
|
63
|
+
attributes[:client_id]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# This is typically empty, as all messages received from Ably are automatically decoded client-side using this value.
|
|
67
|
+
# However, if the message encoding cannot be processed, this attribute contains the remaining transformations
|
|
68
|
+
# not applied to the data payload.
|
|
69
|
+
#
|
|
70
|
+
# @spec TM2e
|
|
71
|
+
#
|
|
72
|
+
# @return [String]
|
|
73
|
+
#
|
|
74
|
+
def encoding
|
|
75
|
+
attributes[:encoding]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The event name.
|
|
79
|
+
#
|
|
80
|
+
# @spec TM2g
|
|
81
|
+
#
|
|
82
|
+
# @return [String]
|
|
83
|
+
#
|
|
84
|
+
def name
|
|
85
|
+
attributes[:name]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The message payload, if provided.
|
|
89
|
+
#
|
|
90
|
+
# @spec TM2d
|
|
91
|
+
#
|
|
92
|
+
# @return [Hash, nil]
|
|
93
|
+
#
|
|
94
|
+
def data
|
|
95
|
+
@data ||= attributes[:data].freeze
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A Unique ID assigned by Ably to this message.
|
|
99
|
+
#
|
|
100
|
+
# @spec TM2a
|
|
101
|
+
#
|
|
102
|
+
# @return [String]
|
|
103
|
+
#
|
|
104
|
+
def id
|
|
105
|
+
attributes.fetch(:id) { "#{protocol_message.id!}:#{protocol_message_index}" }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The connection ID of the publisher of this message.
|
|
109
|
+
#
|
|
110
|
+
# @spec TM2c
|
|
111
|
+
#
|
|
112
|
+
# @return [String]
|
|
113
|
+
#
|
|
114
|
+
def connection_id
|
|
115
|
+
attributes.fetch(:connection_id) { protocol_message.connection_id if assigned_to_protocol_message? }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch.
|
|
119
|
+
#
|
|
120
|
+
# @spec TM2f
|
|
121
|
+
#
|
|
122
|
+
# @return [Integer]
|
|
123
|
+
#
|
|
124
|
+
def timestamp
|
|
125
|
+
if attributes[:timestamp]
|
|
126
|
+
as_time_from_epoch(attributes[:timestamp])
|
|
127
|
+
else
|
|
128
|
+
protocol_message.timestamp
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def attributes
|
|
133
|
+
@attributes
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def to_json(*args)
|
|
137
|
+
as_json(*args).tap do |message|
|
|
138
|
+
decode_binary_data_before_to_json message
|
|
139
|
+
end.to_json
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The size is the sum over name, data, clientId, and extras in bytes (TO3l8a)
|
|
143
|
+
#
|
|
144
|
+
def size
|
|
145
|
+
%w(name data client_id extras).map do |attr|
|
|
146
|
+
if (value = attributes[attr.to_sym]).is_a?(String)
|
|
147
|
+
value.bytesize
|
|
148
|
+
elsif value.nil?
|
|
149
|
+
0
|
|
150
|
+
else
|
|
151
|
+
value.to_json.bytesize
|
|
152
|
+
end
|
|
153
|
+
end.sum
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Assign this message to a ProtocolMessage before delivery to the Ably system
|
|
157
|
+
# @api private
|
|
158
|
+
def assign_to_protocol_message(protocol_message)
|
|
159
|
+
@protocol_message = protocol_message
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# True if this message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
|
|
163
|
+
#
|
|
164
|
+
# @return [Boolean]
|
|
165
|
+
#
|
|
166
|
+
# @api private
|
|
167
|
+
def assigned_to_protocol_message?
|
|
168
|
+
!!@protocol_message
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The optional ProtocolMessage this message is assigned to. If ProtocolMessage is nil, an error will be raised.
|
|
172
|
+
#
|
|
173
|
+
# @return [Ably::Models::ProtocolMessage]
|
|
174
|
+
#
|
|
175
|
+
# @api private
|
|
176
|
+
def protocol_message
|
|
177
|
+
raise RuntimeError, 'Message is not yet published with a ProtocolMessage. ProtocolMessage is nil' if @protocol_message.nil?
|
|
178
|
+
@protocol_message
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Contains any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays.
|
|
182
|
+
# The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push
|
|
183
|
+
# 1.2 adds the delta extension which is of type DeltaExtras, and the headers extension, which contains arbitrary string->string key-value pairs,
|
|
184
|
+
# settable at publish time. Unless otherwise specified, the client library should not attempt to do any filtering or validation of the extras
|
|
185
|
+
# field itself, but should treat it opaquely, encoding it and passing it to realtime unaltered.
|
|
186
|
+
# @api private
|
|
187
|
+
def extras
|
|
188
|
+
attributes[:extras].tap do |val|
|
|
189
|
+
unless val.kind_of?(IdiomaticRubyWrapper) || val.kind_of?(Array) || val.kind_of?(Hash) || val.nil?
|
|
190
|
+
raise ArgumentError, "extras contains an unsupported type #{val.class}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Delta extras extension (TM2i)
|
|
196
|
+
#
|
|
197
|
+
# @return [DeltaExtras, nil]
|
|
198
|
+
#
|
|
199
|
+
# @api private
|
|
200
|
+
def delta_extras
|
|
201
|
+
return nil if attributes[:extras][:delta].nil?
|
|
202
|
+
@delta_extras ||= DeltaExtras.new(attributes[:extras][:delta]).freeze
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def protocol_message_index
|
|
206
|
+
protocol_message.messages.map(&:object_id).index(self.object_id)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
def raw_hash_object
|
|
211
|
+
@raw_hash_object
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def set_attributes_object(new_attributes)
|
|
215
|
+
@attributes = IdiomaticRubyWrapper(new_attributes.clone, stop_at: [:data, :extras])
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def logger
|
|
219
|
+
return @logger if @logger
|
|
220
|
+
protocol_message.logger if protocol_message
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'ably/modules/conversions'
|
|
2
|
+
|
|
3
|
+
# MessageEncoders are registered with the Ably client library and are responsible
|
|
4
|
+
# for encoding & decoding messages.
|
|
5
|
+
#
|
|
6
|
+
# For example, if a message body is detected as JSON, it is encoded as a String and the encoding attribute
|
|
7
|
+
# of the message is defined as 'json'.
|
|
8
|
+
# Encrypted messages are encoded & decoded by the Cipher encoder.
|
|
9
|
+
#
|
|
10
|
+
module Ably
|
|
11
|
+
module Models
|
|
12
|
+
module MessageEncoders
|
|
13
|
+
extend Ably::Modules::Conversions
|
|
14
|
+
|
|
15
|
+
# Base interface for an Ably Encoder
|
|
16
|
+
#
|
|
17
|
+
class Base
|
|
18
|
+
attr_reader :client, :options
|
|
19
|
+
|
|
20
|
+
def initialize(client, options = {})
|
|
21
|
+
@client = client
|
|
22
|
+
@options = options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# #encode is called once before a message is sent to Ably
|
|
26
|
+
#
|
|
27
|
+
# It is the responsibility of the #encode method to detect the intended encoding and modify the :data & :encoding properties of the message object.
|
|
28
|
+
#
|
|
29
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
|
30
|
+
# The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
|
|
31
|
+
# This #encode method should modify the message Hash if any encoding action is to be taken
|
|
32
|
+
# @param [Hash] channel_options the options used to initialize the channel that this message was received on
|
|
33
|
+
#
|
|
34
|
+
# @return [void]
|
|
35
|
+
#
|
|
36
|
+
def encode(message, channel_options)
|
|
37
|
+
raise "Not yet implemented"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# #decode is called once for every encoding step
|
|
41
|
+
# i.e. if message encoding arrives with 'utf-8/cipher+aes-128-cbc/base64'
|
|
42
|
+
# the decoder will call #decode once for each encoding part such as 'base64', then 'cipher+aes-128-cbc', and finally 'utf-8'
|
|
43
|
+
#
|
|
44
|
+
# It is the responsibility of the #decode method to detect the current encoding part and modify the :data & :encoding properties of the message object.
|
|
45
|
+
#
|
|
46
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
|
47
|
+
# The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
|
|
48
|
+
# This #encode method should modify the message Hash if any decoding action is to be taken
|
|
49
|
+
# @param [Hash] channel_options the options used to initialize the channel that this message was received on
|
|
50
|
+
#
|
|
51
|
+
# @return [void]
|
|
52
|
+
#
|
|
53
|
+
def decode(message, channel_options)
|
|
54
|
+
raise "Not yet implemented"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Add encoding to the message Hash.
|
|
58
|
+
# Ensures that encoding delimeter is used where required i.e utf-8/cipher+aes-128-cbc/base64
|
|
59
|
+
#
|
|
60
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
|
61
|
+
# @param [String] encoding encoding to add to the current encoding
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
64
|
+
#
|
|
65
|
+
def add_encoding_to_message(encoding, message)
|
|
66
|
+
message[:encoding] = [message[:encoding], encoding].compact.join('/')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the right most encoding form a meessage encoding, and nil if none exists
|
|
70
|
+
# i.e. current_encoding_part('utf-8/cipher+aes-128-cbc/base64') => 'base64'
|
|
71
|
+
#
|
|
72
|
+
# @return [String,nil]
|
|
73
|
+
#
|
|
74
|
+
def current_encoding_part(message)
|
|
75
|
+
if message[:encoding]
|
|
76
|
+
message[:encoding].split('/')[-1]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Strip the current encoding part within the message Hash.
|
|
81
|
+
#
|
|
82
|
+
# For example, calling this method on an :encoding value of 'utf-8/cipher+aes-128-cbc/base64' would update the attribute
|
|
83
|
+
# :encoding to 'utf-8/cipher+aes-128-cbc'
|
|
84
|
+
#
|
|
85
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
#
|
|
89
|
+
def strip_current_encoding_part(message)
|
|
90
|
+
raise "Cannot strip encoding when there is no encoding for this message" unless message[:encoding]
|
|
91
|
+
message[:encoding] = message[:encoding].split('/')[0...-1].join('/')
|
|
92
|
+
message[:encoding] = nil if message[:encoding].empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# True of the message data payload is empty
|
|
96
|
+
#
|
|
97
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
#
|
|
101
|
+
def is_empty?(message)
|
|
102
|
+
message[:data].nil? || message[:data] == ''
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @api private
|
|
107
|
+
def self.register_default_encoders(client, options = {})
|
|
108
|
+
binary_protocol = !!options[:binary_protocol]
|
|
109
|
+
client.register_encoder Ably::Models::MessageEncoders::Utf8
|
|
110
|
+
client.register_encoder Ably::Models::MessageEncoders::Json
|
|
111
|
+
client.register_encoder Ably::Models::MessageEncoders::Cipher
|
|
112
|
+
client.register_encoder Ably::Models::MessageEncoders::Base64, binary_protocol: binary_protocol
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @api private
|
|
116
|
+
def self.encoder_from(encoder, options)
|
|
117
|
+
encoder_klass = if encoder.kind_of?(String)
|
|
118
|
+
encoder.split('::').inject(Kernel) do |base, klass_name|
|
|
119
|
+
base.public_send(:const_get, klass_name)
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
encoder
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
|
|
126
|
+
encoder_klass.new(self, options)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
require 'ably/models/message_encoders/base64'
|
|
133
|
+
require 'ably/models/message_encoders/cipher'
|
|
134
|
+
require 'ably/models/message_encoders/json'
|
|
135
|
+
require 'ably/models/message_encoders/utf8'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'ably/models/message_encoders/base'
|
|
3
|
+
|
|
4
|
+
module Ably::Models::MessageEncoders
|
|
5
|
+
# Base64 binary Encoder and Decoder
|
|
6
|
+
# Uses encoding identifier 'base64'
|
|
7
|
+
#
|
|
8
|
+
class Base64 < Base
|
|
9
|
+
ENCODING_ID = 'base64'
|
|
10
|
+
|
|
11
|
+
def encode(message, channel_options)
|
|
12
|
+
return if is_empty?(message)
|
|
13
|
+
|
|
14
|
+
if is_binary?(message) && transport_protocol_text?
|
|
15
|
+
message[:data] = ::Base64.encode64(message[:data])
|
|
16
|
+
add_encoding_to_message ENCODING_ID, message
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def decode(message, channel_options)
|
|
21
|
+
if is_base64_encoded?(message)
|
|
22
|
+
message[:data] = ::Base64.decode64(message[:data])
|
|
23
|
+
strip_current_encoding_part message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def is_binary?(message)
|
|
29
|
+
message[:data].kind_of?(String) && message[:data].encoding == Encoding::ASCII_8BIT
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def is_base64_encoded?(message)
|
|
33
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def transport_protocol_text?
|
|
37
|
+
!options[:binary_protocol]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'ably/exceptions'
|
|
2
|
+
require 'ably/models/message_encoders/base'
|
|
3
|
+
require 'ably/util/crypto'
|
|
4
|
+
|
|
5
|
+
module Ably::Models::MessageEncoders
|
|
6
|
+
# Cipher Encoder & Decoder that automatically encrypts & decrypts messages using Ably::Util::Crypto
|
|
7
|
+
# when a channel has the +:cipher+ channel option configured
|
|
8
|
+
#
|
|
9
|
+
class Cipher < Base
|
|
10
|
+
ENCODING_ID = 'cipher'
|
|
11
|
+
|
|
12
|
+
def initialize(*args)
|
|
13
|
+
super
|
|
14
|
+
@cryptos = Hash.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def encode(message, channel_options)
|
|
18
|
+
return if is_empty?(message)
|
|
19
|
+
return if already_encrypted?(message)
|
|
20
|
+
|
|
21
|
+
if channel_configured_for_encryption?(channel_options)
|
|
22
|
+
add_encoding_to_message 'utf-8', message unless is_binary?(message) || is_utf8_encoded?(message)
|
|
23
|
+
crypto = crypto_for(channel_options)
|
|
24
|
+
message[:data] = crypto.encrypt(message[:data])
|
|
25
|
+
add_encoding_to_message "#{ENCODING_ID}+#{crypto.cipher_params.cipher_type.downcase}", message
|
|
26
|
+
end
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
raise Ably::Exceptions::CipherError.new(e.message, nil, 92005)
|
|
29
|
+
rescue RuntimeError => e
|
|
30
|
+
if e.message.match(/unsupported cipher algorithm/i)
|
|
31
|
+
raise Ably::Exceptions::CipherError.new(e.message, nil, 92004)
|
|
32
|
+
else
|
|
33
|
+
raise e
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def decode(message, channel_options)
|
|
38
|
+
if is_cipher_encoded?(message)
|
|
39
|
+
unless channel_configured_for_encryption?(channel_options)
|
|
40
|
+
raise Ably::Exceptions::CipherError.new('Message cannot be decrypted as the channel is not set up for encryption & decryption', nil, 92001)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
crypto = crypto_for(channel_options)
|
|
44
|
+
unless crypto.cipher_params.cipher_type == cipher_algorithm(message).upcase
|
|
45
|
+
raise Ably::Exceptions::CipherError.new("Cipher algorithm #{crypto.cipher_params.cipher_type} does not match message cipher algorithm of #{cipher_algorithm(message).upcase}", nil, 92002)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
message[:data] = crypto.decrypt(message[:data])
|
|
49
|
+
strip_current_encoding_part message
|
|
50
|
+
end
|
|
51
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
52
|
+
raise Ably::Exceptions::CipherError.new("CipherError decrypting data, the private key may not be correct", nil, 92003)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
def is_binary?(message)
|
|
57
|
+
message.fetch(:data, '').encoding == Encoding::ASCII_8BIT
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def is_utf8_encoded?(message)
|
|
61
|
+
current_encoding_part(message).to_s.match(/^utf-8$/i)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def crypto_for(channel_options)
|
|
65
|
+
@cryptos[channel_options.to_s] ||= Ably::Util::Crypto.new(channel_options.fetch(:cipher, {}))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def channel_configured_for_encryption?(channel_options)
|
|
69
|
+
channel_options[:cipher]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def is_cipher_encoded?(message)
|
|
73
|
+
!cipher_algorithm(message).nil?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def cipher_algorithm(message)
|
|
77
|
+
current_encoding_part(message).to_s[/^#{ENCODING_ID}\+([\w-]+)$/, 1]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def already_encrypted?(message)
|
|
81
|
+
message.fetch(:encoding, '').to_s.match(%r{(^|/)#{ENCODING_ID}\+([\w-]+)($|/)})
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'ably/models/message_encoders/base'
|
|
3
|
+
|
|
4
|
+
module Ably::Models::MessageEncoders
|
|
5
|
+
# JSON Encoder and Decoder
|
|
6
|
+
# Uses encoding identifier 'json' and encodes all objects that are not strings or byte arrays
|
|
7
|
+
#
|
|
8
|
+
class Json < Base
|
|
9
|
+
ENCODING_ID = 'json'
|
|
10
|
+
|
|
11
|
+
def encode(message, channel_options)
|
|
12
|
+
if needs_json_encoding?(message)
|
|
13
|
+
message[:data] = ::JSON.dump(message[:data])
|
|
14
|
+
add_encoding_to_message ENCODING_ID, message
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def decode(message, channel_options)
|
|
19
|
+
if is_json_encoded?(message)
|
|
20
|
+
message[:data] = ::JSON.parse(message[:data])
|
|
21
|
+
strip_current_encoding_part message
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def needs_json_encoding?(message)
|
|
27
|
+
!message[:data].kind_of?(String) && !message[:data].nil?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def is_json_encoded?(message)
|
|
31
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'ably/models/message_encoders/base'
|
|
2
|
+
|
|
3
|
+
module Ably::Models::MessageEncoders
|
|
4
|
+
# Utf8 Encoder and Decoder
|
|
5
|
+
# Uses encoding identifier 'utf-8' and encodes all JSON objects as UTF-8, and sets the encoding when decoding
|
|
6
|
+
#
|
|
7
|
+
class Utf8 < Base
|
|
8
|
+
ENCODING_ID = 'utf-8'
|
|
9
|
+
|
|
10
|
+
def encode(message, channel_options)
|
|
11
|
+
# no encoding of UTF-8 required
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def decode(message, channel_options)
|
|
15
|
+
if is_utf8_encoded?(message)
|
|
16
|
+
message[:data] = message[:data].force_encoding(Encoding::UTF_8)
|
|
17
|
+
strip_current_encoding_part message
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def is_utf8_encoded?(message)
|
|
23
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Ably::Models
|
|
2
|
+
# When Log Level set to none, this NilLogger is used to silence all logging
|
|
3
|
+
# NilLogger provides a Ruby Logger compatible interface
|
|
4
|
+
class NilLogger
|
|
5
|
+
def null_method(*args)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def level
|
|
9
|
+
:none
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def level=(value)
|
|
13
|
+
level
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
[:fatal, :error, :warn, :info, :debug].each do |method|
|
|
17
|
+
alias_method method, :null_method
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|