selfsdk 0.0.209 → 0.0.210

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4adb1d39e345e27532a012a26dcd8711fc245b2e93baa81890c6539cd8cf5424
4
- data.tar.gz: d2e725fb3ab955237186ba7c5d9eca6e2083e4a4144b4a1f7d59b96f92e7ee59
3
+ metadata.gz: 97a576e450b5c015dd14fdac839bdffa8de9a3f6d5fdcda6f9c09970a9a11e63
4
+ data.tar.gz: 02eb35df5ad3fb4609ad5b91704c8085b74532434ca94c458d88fa1f35e62aa9
5
5
  SHA512:
6
- metadata.gz: a916f8c596495f580ee4b29c8e253325be6409133bb5eafb7df398b8848aef5148acc44cee6302d3ad25a1136c9e2344add606290ad49f2bc0be7a081b83b939
7
- data.tar.gz: 51790af7cb7fb53f911c7484584deb733f4d55b9d151b23a4086cab356946cf95c066449a307dd20be755ea33a0db0d287596bb247414446de89f2d3b8866e9e
6
+ metadata.gz: 01335ef6d7e1afcc0abcd3e74d3ff15073ba24b13442475374e28263c02e7a5ba3c2face85ac3140aa8dee54bedb54f81ae0d0a1acfc28c40cb24177e5021456
7
+ data.tar.gz: 344a4b3becca938ff1c23dbe9b29216c7e04d2c31d3bfc986a8bdec3421b94c5ae434bbbec18a3abd071ec17d95cf4a22c841ec3607ce7e979df69c7cb644ca6
data/lib/acl.rb CHANGED
@@ -25,30 +25,43 @@ module SelfSDK
25
25
  def allow(id)
26
26
  @acl_rules << id
27
27
  SelfSDK.logger.info "Allowing connections from #{id}"
28
- @messaging.add_acl_rule(@jwt.prepare(jti: SecureRandom.uuid,
29
- cid: SecureRandom.uuid,
30
- typ: 'acl.permit',
31
- iss: @jwt.id,
32
- sub: @jwt.id,
33
- iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
34
- exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
35
- acl_source: id,
36
- acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339))
28
+ payload = @jwt.prepare(jti: SecureRandom.uuid,
29
+ cid: SecureRandom.uuid,
30
+ typ: 'acl.permit',
31
+ iss: @jwt.id,
32
+ sub: @jwt.id,
33
+ iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
34
+ exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
35
+ acl_source: id,
36
+ acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339)
37
+
38
+ a = SelfMsg::Acl.new
39
+ a.id = SecureRandom.uuid
40
+ a.command = SelfMsg::AclCommandPERMIT
41
+ a.payload = payload
42
+
43
+ @messaging.send_message a
37
44
  end
38
45
 
39
46
  # Deny incomming messages from the given identity.
40
47
  def deny(id)
41
48
  @acl_rules.delete(id)
42
49
  SelfSDK.logger.info "Denying connections from #{id}"
43
- @messaging.remove_acl_rule(@jwt.prepare(jti: SecureRandom.uuid,
44
- cid: SecureRandom.uuid,
45
- typ: 'acl.revoke',
46
- iss: @jwt.id,
47
- sub: @jwt.id,
48
- iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
49
- exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
50
- acl_source: id,
51
- acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339))
50
+ payload = @jwt.prepare(jti: SecureRandom.uuid,
51
+ cid: SecureRandom.uuid,
52
+ typ: 'acl.revoke',
53
+ iss: @jwt.id,
54
+ sub: @jwt.id,
55
+ iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
56
+ exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
57
+ acl_source: id,
58
+ acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339)
59
+
60
+ a = SelfMsg::Acl.new
61
+ a.id = SecureRandom.uuid
62
+ a.command = SelfMsg::AclCommandREVOKE
63
+ a.payload = payload
64
+ @messaging.send_message a
52
65
  end
53
66
  end
54
67
  end
data/lib/client.rb CHANGED
@@ -52,7 +52,7 @@ module SelfSDK
52
52
  res = get "/v1/identities/#{id}/devices"
53
53
  body = JSON.parse(res.body, symbolize_names: true)
54
54
  if res.code != 200
55
- SelfSDK.logger.error "identity response : #{body[:message]}"
55
+ SelfSDK.logger.debug "getting devices response : #{body[:message]}"
56
56
  raise "you need connection permissions"
57
57
  end
58
58
  body
@@ -68,31 +68,43 @@ module SelfSDK
68
68
  end
69
69
 
70
70
  def post(endpoint, body)
71
- res = nil
72
- loop do
73
- res = HTTParty.post("#{@self_url}#{endpoint}",
71
+ safe_request do
72
+ HTTParty.post("#{@self_url}#{endpoint}",
74
73
  headers: {
75
- 'Content-Type' => 'application/json',
76
- 'Authorization' => "Bearer #{@jwt.auth_token}"
74
+ 'Content-Type' => 'application/json',
75
+ 'Authorization' => "Bearer #{@jwt.auth_token}"
77
76
  },
78
77
  body: body)
79
- break if res.code != 503
80
- sleep 2
81
78
  end
82
- return res
83
79
  end
84
80
 
85
81
  def get(endpoint)
82
+ safe_request do
83
+ HTTParty.get("#{@self_url}#{endpoint}",
84
+ headers: {
85
+ 'Content-Type' => 'application/json',
86
+ 'Authorization' => "Bearer #{@jwt.auth_token}"
87
+ })
88
+ end
89
+ end
90
+
91
+ def safe_request(&block)
86
92
  res = nil
87
93
  loop do
88
- res = HTTParty.get("#{@self_url}#{endpoint}", headers: {
89
- 'Content-Type' => 'application/json',
90
- 'Authorization' => "Bearer #{@jwt.auth_token}"
91
- })
92
- break if res.code != 503
94
+ begin
95
+ res = block.call
96
+
97
+ break if res.code == 200
98
+ next if res.code == 503
99
+ next if body[:error_id] == 'token_expired'
100
+
101
+ break
102
+ rescue StandardError => e
103
+ # retry if the server is down
104
+ end
93
105
  sleep 2
94
106
  end
95
- return res
107
+ res
96
108
  end
97
109
 
98
110
  # Lists all public keys stored on self for the given ID
data/lib/crypto.rb CHANGED
@@ -9,6 +9,8 @@ module SelfSDK
9
9
  @device = device
10
10
  @storage_key = storage_key
11
11
  @storage_folder = "#{storage_folder}/#{@client.jwt.key_id}"
12
+ @lock_strategy = true
13
+ @mode = "r+"
12
14
 
13
15
  if File.exist?(account_path)
14
16
  # 1a) if alice's account file exists load the pickle from the file
@@ -33,68 +35,111 @@ module SelfSDK
33
35
  end
34
36
 
35
37
  def encrypt(message, recipients)
36
- ::SelfSDK.logger.debug('encrypting a message')
38
+ ::SelfSDK.logger.debug('- [crypto] encrypting a message')
37
39
 
38
40
  # create a group session and set the identity of the account youre using
39
- ::SelfSDK.logger.debug('create a group session and set the identity of the account youre using')
41
+ ::SelfSDK.logger.debug('- [crypto] create a group session and set the identity of the account youre using')
40
42
  gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
41
43
 
42
44
  sessions = {}
43
- ::SelfSDK.logger.debug('managing sessions with all recipients')
45
+ locks = {}
46
+ ::SelfSDK.logger.debug('- [crypto] managing sessions with all recipients')
47
+
44
48
  recipients.each do |r|
49
+ f = nil
50
+ next if r[:id] == @client.jwt.id && r[:device_id] == @device
51
+
45
52
  session_file_name = session_path(r[:id], r[:device_id])
46
53
  session_with_bob = nil
47
54
 
48
55
  begin
49
- session_with_bob = get_outbound_session_with_bob(r[:id], r[:device_id], session_file_name)
56
+ if File.exist?(session_file_name)
57
+ # Lock the session file
58
+ locks[session_file_name] = File.open(session_file_name, @mode)
59
+ locks[session_file_name].flock(File::LOCK_EX)
60
+ end
61
+ session_with_bob = get_outbound_session_with_bob(locks[session_file_name], r[:id], r[:device_id])
50
62
  rescue => e
51
- ::SelfSDK.logger.warn(" there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
52
- ::SelfSDK.logger.warn(e)
63
+ ::SelfSDK.logger.warn("- [crypto] there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
64
+ ::SelfSDK.logger.warn("- [crypto] #{e}")
53
65
  next
54
66
  end
55
67
 
56
- ::SelfSDK.logger.debug(" adding group participant #{r[:id]}:#{r[:device_id]}")
68
+ ::SelfSDK.logger.debug("- [crypto] adding group participant #{r[:id]}:#{r[:device_id]}")
57
69
  gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
58
70
  sessions[session_file_name] = session_with_bob
59
71
  end
60
72
 
61
73
  # 5) encrypt a message
62
- ::SelfSDK.logger.debug("group encrypting message")
74
+ ::SelfSDK.logger.debug("- [crypto] group encrypting message")
63
75
  ct = gs.encrypt(message).to_s
64
76
 
65
77
  # 6) store the session to a file
66
- ::SelfSDK.logger.debug("storing sessions")
78
+ ::SelfSDK.logger.debug("- [crypto] storing sessions")
67
79
  sessions.each do |session_file_name, session_with_bob|
68
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
80
+ pickle = session_with_bob.to_pickle(@storage_key)
81
+ if locks[session_file_name]
82
+ locks[session_file_name].rewind
83
+ locks[session_file_name].write(pickle)
84
+ locks[session_file_name].truncate(locks[session_file_name].pos)
85
+ else
86
+ File.write(session_file_name, pickle)
87
+ end
69
88
  end
70
89
 
71
90
  ct
91
+ ensure
92
+ locks.each do |session_file_name, lock|
93
+ # Unlock the file
94
+ if lock
95
+ lock.flock(File::LOCK_UN)
96
+ end
97
+ end
72
98
  end
73
99
 
74
100
  def decrypt(message, sender, sender_device)
75
- ::SelfSDK.logger.debug("decrypting a message")
101
+ f = nil
102
+ ::SelfSDK.logger.debug("- [crypto] decrypting a message")
76
103
  session_file_name = session_path(sender, sender_device)
77
104
 
78
- ::SelfSDK.logger.debug("loading sessions")
79
- session_with_bob = get_inbound_session_with_bob(message, session_file_name)
105
+ if File.exist?(session_file_name)
106
+ # Lock the session file
107
+ f = File.open(session_file_name, @mode)
108
+ f.flock(File::LOCK_EX)
109
+ end
110
+
111
+ ::SelfSDK.logger.debug("- [crypto] loading sessions")
112
+ session_with_bob = get_inbound_session_with_bob(f, message)
80
113
 
81
114
  # 8) create a group session and set the identity of the account you're using
82
- ::SelfSDK.logger.debug("create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
115
+ ::SelfSDK.logger.debug("- [crypto] create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
83
116
  gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
84
117
 
85
118
  # 9) add all recipients and their sessions
86
- ::SelfSDK.logger.debug("add all recipients and their sessions #{@sender}:#{@sender_device}")
119
+ ::SelfSDK.logger.debug("- [crypto] add all recipients and their sessions #{@sender}:#{@sender_device}")
87
120
  gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
88
121
 
89
122
  # 10) decrypt the message ciphertext
90
- ::SelfSDK.logger.debug("decrypt the message ciphertext")
123
+ ::SelfSDK.logger.debug("- [crypto] decrypt the message ciphertext")
91
124
  pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
92
125
 
93
126
  # 11) store the session to a file
94
- ::SelfSDK.logger.debug("store the session to a file")
95
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
127
+ ::SelfSDK.logger.debug("- [crypto] store the session to a file")
128
+
129
+ pickle = session_with_bob.to_pickle(@storage_key)
130
+ if !f.nil?
131
+ f.rewind
132
+ f.write(pickle)
133
+ f.truncate(f.pos)
134
+ else
135
+ File.write(session_file_name, pickle)
136
+ end
96
137
 
97
138
  pt
139
+ ensure
140
+ # Unlock the session file
141
+ f&.flock(File::LOCK_UN)
142
+ f&.close
98
143
  end
99
144
 
100
145
  private
@@ -107,10 +152,11 @@ module SelfSDK
107
152
  "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
108
153
  end
109
154
 
110
- def get_outbound_session_with_bob(recipient, recipient_device, session_file_name)
111
- if File.exist?(session_file_name)
155
+ def get_outbound_session_with_bob(f, recipient, recipient_device)
156
+ if !f.nil?
157
+ pickle = f.read
112
158
  # 2a) if bob's session file exists load the pickle from the file
113
- session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
159
+ session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
114
160
  else
115
161
  # 2b-i) if you have not previously sent or recevied a message to/from bob,
116
162
  # you must get his identity key from GET /v1/identities/bob/
@@ -121,7 +167,7 @@ module SelfSDK
121
167
 
122
168
  if res.code != 200
123
169
  b = JSON.parse(res.body)
124
- ::SelfSDK.logger.error "identity response : #{b['message']}"
170
+ ::SelfSDK.logger.error "- [crypto] identity response : #{b['message']}"
125
171
  raise "could not get identity pre_keys"
126
172
  end
127
173
 
@@ -137,10 +183,11 @@ module SelfSDK
137
183
  session_with_bob
138
184
  end
139
185
 
140
- def get_inbound_session_with_bob(message, session_file_name)
141
- if File.exist?(session_file_name)
186
+ def get_inbound_session_with_bob(f, message)
187
+ if !f.nil?
188
+ pickle = f.read
142
189
  # 7a) if carol's session file exists load the pickle from the file
143
- session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
190
+ session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
144
191
  end
145
192
 
146
193
  # 7b-i) if you have not previously sent or received a message to/from bob,
data/lib/messages/base.rb CHANGED
@@ -79,7 +79,7 @@ module SelfSDK
79
79
  end
80
80
  end
81
81
  raise ::StandardError.new("expired message") if @expires < SelfSDK::Time.now
82
- raise ::StandardError.new("issued too soon") if @issued > SelfSDK::Time.now
82
+ raise ::StandardError.new("issued too soon") if @issued.round > (SelfSDK::Time.now + 1).round
83
83
  end
84
84
 
85
85
  protected
@@ -21,6 +21,9 @@ require_relative "connection_response"
21
21
 
22
22
  module SelfSDK
23
23
  module Messages
24
+ class UnmappedMessage < StandardError
25
+ end
26
+
24
27
  def self.parse(input, messaging, original=nil)
25
28
  envelope = nil
26
29
  body = if input.is_a? String
@@ -84,7 +87,7 @@ module SelfSDK
84
87
  m = VoiceSummary.new(messaging)
85
88
  m.parse(body, envelope)
86
89
  else
87
- raise StandardError.new("Invalid message type #{payload[:typ]}.")
90
+ raise UnmappedMessage.new("Invalid message type #{payload[:typ]}.")
88
91
  end
89
92
  return m
90
93
  end
data/lib/messaging.rb CHANGED
@@ -11,28 +11,91 @@ require_relative 'crypto'
11
11
  require_relative 'messages/message'
12
12
 
13
13
  module SelfSDK
14
+ class WebsocketClient
15
+ ON_DEMAND_CLOSE_CODE=3999
16
+
17
+ attr_accessor :ws
18
+
19
+ def initialize(url, auto_reconnect, authentication_hook, process_message_hook)
20
+ @url = url
21
+ @reconnection_delay = nil
22
+ @auto_reconnect = auto_reconnect
23
+ @authentication_hook = authentication_hook
24
+ @process_message_hook = process_message_hook
25
+ end
26
+
27
+ # Creates a websocket connection and sets up its callbacks.
28
+ def start
29
+ SelfSDK.logger.debug "starting listener"
30
+ @ws = Faye::WebSocket::Client.new(@url)
31
+ SelfSDK.logger.debug "initialized"
32
+
33
+ @ws.on :open do |_event|
34
+ SelfSDK.logger.debug "websocket connection established"
35
+ @authentication_hook.call
36
+ end
37
+
38
+ @ws.on :message do |event|
39
+ @process_message_hook.call(event)
40
+ end
41
+
42
+ @ws.on :close do |event|
43
+ SelfSDK.logger.debug "connection closed detected : #{event.code} #{event.reason}"
44
+
45
+ if event.code != ON_DEMAND_CLOSE_CODE
46
+ raise StandardError('websocket connection closed') if !@auto_reconnect
47
+
48
+ if !@reconnection_delay.nil?
49
+ SelfSDK.logger.debug "websocket connection closed (#{event.code}) #{event.reason}"
50
+ sleep @reconnection_delay
51
+ SelfSDK.logger.debug "reconnecting..."
52
+ end
53
+
54
+ @reconnection_delay = 3
55
+ start
56
+ end
57
+ end
58
+ end
59
+
60
+ # Sends a closing message to the websocket client.
61
+ def close
62
+ @ws.close(ON_DEMAND_CLOSE_CODE, "connection closed by the client")
63
+ end
64
+
65
+ # Sends a ping message to the websocket server, but does
66
+ # not expect response.
67
+ # This is kind of a hack to catch some corner cases where
68
+ # the websocket client is not aware it has been disconnected.
69
+ def ping
70
+ SelfSDK.logger.debug "ping"
71
+ @ws.ping 'ping'
72
+ end
73
+
74
+ def send(message)
75
+ @ws.send(message.to_fb.bytes)
76
+ end
77
+ end
78
+
14
79
  class MessagingClient
15
80
  DEFAULT_DEVICE="1"
16
81
  DEFAULT_AUTO_RECONNECT=true
17
82
  DEFAULT_STORAGE_DIR="./.self_storage"
18
- ON_DEMAND_CLOSE_CODE=3999
19
83
 
20
- PRIORITIES = {
21
- "chat.invite": SelfSDK::Messages::PRIORITY_VISIBLE,
22
- "chat.join": SelfSDK::Messages::PRIORITY_INVISIBLE,
23
- "chat.message": SelfSDK::Messages::PRIORITY_VISIBLE,
24
- "chat.message.delete": SelfSDK::Messages::PRIORITY_INVISIBLE,
25
- "chat.message.delivered": SelfSDK::Messages::PRIORITY_INVISIBLE,
26
- "chat.message.edit": SelfSDK::Messages::PRIORITY_INVISIBLE,
27
- "chat.message.read": SelfSDK::Messages::PRIORITY_INVISIBLE,
28
- "chat.remove": SelfSDK::Messages::PRIORITY_INVISIBLE,
29
- "document.sign.req": SelfSDK::Messages::PRIORITY_VISIBLE,
30
- "identities.authenticate.req": SelfSDK::Messages::PRIORITY_VISIBLE,
31
- "identities.connections.req": SelfSDK::Messages::PRIORITY_VISIBLE,
32
- "identities.facts.query.req": SelfSDK::Messages::PRIORITY_VISIBLE,
33
- "identities.facts.issue": SelfSDK::Messages::PRIORITY_VISIBLE,
34
- "identities.notify": SelfSDK::Messages::PRIORITY_VISIBLE
35
- }
84
+ PRIORITIES = {
85
+ 'chat.invite': SelfSDK::Messages::PRIORITY_VISIBLE,
86
+ 'chat.join': SelfSDK::Messages::PRIORITY_INVISIBLE,
87
+ 'chat.message': SelfSDK::Messages::PRIORITY_VISIBLE,
88
+ 'chat.message.delete': SelfSDK::Messages::PRIORITY_INVISIBLE,
89
+ 'chat.message.delivered': SelfSDK::Messages::PRIORITY_INVISIBLE,
90
+ 'chat.message.edit': SelfSDK::Messages::PRIORITY_INVISIBLE,
91
+ 'chat.message.read': SelfSDK::Messages::PRIORITY_INVISIBLE,
92
+ 'chat.remove': SelfSDK::Messages::PRIORITY_INVISIBLE,
93
+ 'document.sign.req': SelfSDK::Messages::PRIORITY_VISIBLE,
94
+ 'identities.authenticate.req': SelfSDK::Messages::PRIORITY_VISIBLE,
95
+ 'identities.connections.req': SelfSDK::Messages::PRIORITY_VISIBLE,
96
+ 'identities.facts.query.req': SelfSDK::Messages::PRIORITY_VISIBLE,
97
+ 'identities.facts.issue': SelfSDK::Messages::PRIORITY_VISIBLE,
98
+ 'identities.notify': SelfSDK::Messages::PRIORITY_VISIBLE }.freeze
36
99
 
37
100
  attr_accessor :client, :jwt, :device_id, :ack_timeout, :timeout, :type_observer, :uuid_observer, :encryption_client, :source
38
101
 
@@ -47,7 +110,6 @@ module SelfSDK
47
110
  # @option opts [String] :device_id The device id to be used by the app defaults to "1".
48
111
  def initialize(url, client, storage_key, options = {})
49
112
  @mon = Monitor.new
50
- @url = url
51
113
  @messages = {}
52
114
  @acks = {}
53
115
  @type_observer = {}
@@ -58,14 +120,12 @@ module SelfSDK
58
120
  @timeout = 120 # seconds
59
121
  @auth_id = SecureRandom.uuid
60
122
  @device_id = options.fetch(:device_id, DEFAULT_DEVICE)
61
- @auto_reconnect = options.fetch(:auto_reconnect, DEFAULT_AUTO_RECONNECT)
62
123
  @raw_storage_dir = options.fetch(:storage_dir, DEFAULT_STORAGE_DIR)
63
124
  @storage_dir = "#{@raw_storage_dir}/apps/#{@jwt.id}/devices/#{@device_id}"
64
125
  FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
65
126
  @offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
66
127
  @offset = read_offset
67
128
  @source = SelfSDK::Sources.new("#{__dir__}/sources.json")
68
- migrate_old_storage_format
69
129
 
70
130
  unless options.include? :no_crypto
71
131
  crypto_path = "#{@storage_dir}/keys"
@@ -73,13 +133,55 @@ module SelfSDK
73
133
  @encryption_client = Crypto.new(@client, @device_id, crypto_path, storage_key)
74
134
  end
75
135
 
76
- if options.include? :ws
77
- @ws = options[:ws]
78
- else
79
- start
136
+ @ws = if options.include? :ws
137
+ options[:ws]
138
+ else
139
+ WebsocketClient.new(url,
140
+ options.fetch(:auto_reconnect, DEFAULT_AUTO_RECONNECT),
141
+ -> { authenticate },
142
+ ->(event) { on_message(event) })
143
+ end
144
+ end
145
+
146
+ # Starts the underlying websocket connection.
147
+ def start
148
+ SelfSDK.logger.debug "starting"
149
+ auth_id = @auth_id.dup
150
+
151
+ @mon.synchronize do
152
+ @acks[auth_id] = { waiting_cond: @mon.new_cond,
153
+ waiting: true,
154
+ timeout: SelfSDK::Time.now + @ack_timeout }
155
+ end
156
+
157
+ Thread.new do
158
+ EM.run start_connection
80
159
  end
160
+
161
+ Thread.new do
162
+ loop do
163
+ sleep 10
164
+ clean_timeouts
165
+ @ws.ping
166
+ end
167
+ end
168
+
169
+ @mon.synchronize do
170
+ @acks[auth_id][:waiting_cond].wait_while { @acks[auth_id][:waiting] }
171
+ @acks.delete(auth_id)
172
+ end
173
+
174
+ return unless @acks.include? auth_id
175
+
176
+ # In case this does not succeed start the process again.
177
+ if @acks[auth_id][:waiting]
178
+ close
179
+ start_connection
180
+ end
181
+ @acks.delete(auth_id)
81
182
  end
82
183
 
184
+ # Stops the underlying websocket connection.
83
185
  def stop
84
186
  @acks.each do |k, _v|
85
187
  mark_as_acknowledged(k)
@@ -90,27 +192,7 @@ module SelfSDK
90
192
  end
91
193
  end
92
194
 
93
- def close
94
- @ws.close(ON_DEMAND_CLOSE_CODE, "connection closed by the client")
95
- end
96
-
97
- # Responds a request information request
98
- #
99
- # @param recipient [string] selfID to be requested
100
- # @param recipient_device [string] device id for the selfID to be requested
101
- # @param request [string] original message requesing information
102
- def share_information(recipient, recipient_device, request)
103
- m = SelfMsg::Message.new
104
- m.id = SecureRandom.uuid
105
- m.sender = "#{@jwt.id}:#{@device_id}"
106
- m.recipient = "#{recipient}:#{recipient_device}"
107
- m.message_type = "identities.facts.query.resp"
108
- m.priority = select_priority(m.message_type)
109
- m.ciphertext = @jwt.prepare(request)
110
-
111
- send_message m
112
- end
113
-
195
+ # Checks if the session with a specified identity / device is already created.
114
196
  def session?(identifier, device)
115
197
  path = @encryption_client.session_path(identifier, device)
116
198
  File.file?(path)
@@ -136,7 +218,7 @@ module SelfSDK
136
218
  end
137
219
  end
138
220
 
139
- SelfSDK.logger.info "sending custom message #{request_body.to_json}"
221
+ SelfSDK.logger.debug "sending custom message #{request_body.to_json}"
140
222
  current_device = "#{@jwt.id}:#{@device_id}"
141
223
 
142
224
  recs.each do |r|
@@ -154,43 +236,19 @@ module SelfSDK
154
236
  m.message_type = r[:typ]
155
237
  m.priority = select_priority(r[:typ])
156
238
 
157
- SelfSDK.logger.info " -> to #{m.recipient}"
239
+ SelfSDK.logger.debug "[#{m.id}] -> to #{m.recipient}"
158
240
  send_message m
159
241
  end
160
242
  end
161
243
 
162
- # Allows incomming messages from the given identity
163
- #
164
- # @params payload [string] base64 encoded payload to be sent
165
- def add_acl_rule(payload)
166
- a = SelfMsg::Acl.new
167
- a.id = SecureRandom.uuid
168
- a.command = SelfMsg::AclCommandPERMIT
169
- a.payload = payload
170
-
171
- send_message a
172
- end
173
-
174
- # Blocks incoming messages from specified identities
175
- #
176
- # @params payload [string] base64 encoded payload to be sent
177
- def remove_acl_rule(payload)
178
- a = SelfMsg::Acl.new
179
- a.id = SecureRandom.uuid
180
- a.command = SelfMsg::AclCommandREVOKE
181
- a.payload = payload
182
-
183
- send_message a
184
- end
185
-
186
- # Lists acl rules
244
+ # Sends a command to list ACL rules.
187
245
  def list_acl_rules
188
246
  wait_for 'acl_list' do
189
247
  a = SelfMsg::Acl.new
190
248
  a.id = SecureRandom.uuid
191
249
  a.command = SelfMsg::AclCommandLIST
192
250
 
193
- send_raw a
251
+ @ws.send a
194
252
  end
195
253
  end
196
254
 
@@ -198,7 +256,7 @@ module SelfSDK
198
256
  #
199
257
  # @params msg [SelfMsg::Message] message object to be sent
200
258
  def send_and_wait_for_response(msgs, original)
201
- SelfSDK.logger.info "sending/wait for #{msgs.first.id}"
259
+ SelfSDK.logger.debug "sending/wait for #{msgs.first.id}"
202
260
  wait_for msgs.first.id, original do
203
261
  msgs.each do |msg|
204
262
  send_message msg
@@ -211,7 +269,7 @@ module SelfSDK
211
269
  #
212
270
  # @params uuid [string] unique identifier for a conversation
213
271
  def wait_for(uuid, msg = nil)
214
- SelfSDK.logger.info "sending #{uuid}"
272
+ SelfSDK.logger.debug "sending #{uuid}"
215
273
  @mon.synchronize do
216
274
  @messages[uuid] = {
217
275
  waiting_cond: @mon.new_cond,
@@ -223,14 +281,14 @@ module SelfSDK
223
281
 
224
282
  yield
225
283
 
226
- SelfSDK.logger.info "waiting for client to respond #{uuid}"
284
+ SelfSDK.logger.debug "waiting for client to respond #{uuid}"
227
285
  @mon.synchronize do
228
286
  @messages[uuid][:waiting_cond].wait_while do
229
287
  @messages[uuid][:waiting]
230
288
  end
231
289
  end
232
290
 
233
- SelfSDK.logger.info "response received for #{uuid}"
291
+ SelfSDK.logger.debug "response received for #{uuid}"
234
292
  @messages[uuid][:response]
235
293
  ensure
236
294
  @messages.delete(uuid)
@@ -248,14 +306,21 @@ module SelfSDK
248
306
  timeout: SelfSDK::Time.now + @ack_timeout,
249
307
  }
250
308
  end
251
- send_raw(msg)
252
- SelfSDK.logger.info "waiting for acknowledgement #{uuid}"
309
+ @ws.send msg
310
+ SelfSDK.logger.debug "waiting for acknowledgement #{uuid}"
253
311
  @mon.synchronize do
254
312
  @acks[uuid][:waiting_cond].wait_while do
255
313
  @acks[uuid][:waiting]
256
314
  end
257
315
  end
258
- SelfSDK.logger.info "acknowledged #{uuid}"
316
+
317
+ # response has timed out
318
+ if @acks[uuid][:timed_out]
319
+ SelfSDK.logger.debug "acknowledgement response timed out re-sending message #{uuid}"
320
+ return send_message(msg)
321
+ end
322
+
323
+ SelfSDK.logger.debug "acknowledged #{uuid}"
259
324
  true
260
325
  ensure
261
326
  @acks.delete(uuid)
@@ -281,6 +346,7 @@ module SelfSDK
281
346
  # Notify the type observer for the given message
282
347
  def notify_observer(message)
283
348
  if @uuid_observer.include? message.id
349
+ SelfSDK.logger.debug " - notifying by id"
284
350
  observer = @uuid_observer[message.id]
285
351
  message.validate!(observer[:original_message]) if observer.include?(:original_message)
286
352
  Thread.new do
@@ -290,9 +356,15 @@ module SelfSDK
290
356
  return
291
357
  end
292
358
 
359
+ SelfSDK.logger.debug " - notifying by type"
360
+ SelfSDK.logger.debug " - #{message.typ}"
361
+ SelfSDK.logger.debug " - #{message}"
362
+ SelfSDK.logger.debug " - #{@type_observer.keys.join(',')}"
363
+
293
364
  # Return if there is no observer setup for this kind of message
294
365
  return unless @type_observer.include? message.typ
295
366
 
367
+ SelfSDK.logger.debug " - notifying by type (Y)"
296
368
  Thread.new do
297
369
  @type_observer[message.typ][:block].call(message)
298
370
  end
@@ -310,38 +382,6 @@ module SelfSDK
310
382
 
311
383
  private
312
384
 
313
- # Start sthe websocket listener
314
- def start
315
- SelfSDK.logger.info "starting"
316
- auth_id = @auth_id.dup
317
-
318
- @mon.synchronize do
319
- @acks[auth_id] = { waiting_cond: @mon.new_cond,
320
- waiting: true,
321
- timeout: SelfSDK::Time.now + @ack_timeout }
322
- end
323
-
324
- Thread.new do
325
- EM.run start_connection
326
- end
327
-
328
- Thread.new do
329
- loop { sleep 10; clean_timeouts }
330
- end
331
-
332
- @mon.synchronize do
333
- @acks[auth_id][:waiting_cond].wait_while { @acks[auth_id][:waiting] }
334
- @acks.delete(auth_id)
335
- end
336
- # In case this does not succeed start the process again.
337
- if @acks.include? auth_id
338
- if @acks[auth_id][:waiting]
339
- close
340
- start_connection
341
- end
342
- @acks.delete(auth_id)
343
- end
344
- end
345
385
 
346
386
  # Cleans expired messages
347
387
  def clean_timeouts
@@ -355,44 +395,17 @@ module SelfSDK
355
395
  next unless list[uuid][:timeout] < SelfSDK::Time.now
356
396
 
357
397
  @mon.synchronize do
358
- SelfSDK.logger.info "message response timed out #{uuid}"
398
+ SelfSDK.logger.debug "[#{uuid}] message response timed out"
359
399
  list[uuid][:waiting] = false
360
400
  list[uuid][:waiting_cond].broadcast
401
+ list[uuid][:timed_out] = true
361
402
  end
362
403
  end
363
404
  end
364
405
 
365
406
  # Creates a websocket connection and sets up its callbacks.
366
407
  def start_connection
367
- SelfSDK.logger.info "starting listener"
368
- @ws = Faye::WebSocket::Client.new(@url)
369
- SelfSDK.logger.info "initialized"
370
-
371
- @ws.on :open do |_event|
372
- SelfSDK.logger.info "websocket connection established"
373
- authenticate
374
- end
375
-
376
- @ws.on :message do |event|
377
- on_message(event)
378
- end
379
-
380
- @ws.on :close do |event|
381
- if event.code == ON_DEMAND_CLOSE_CODE
382
- puts "client closed connection"
383
- else
384
- if !@auto_reconnect
385
- raise StandardError "websocket connection closed"
386
- end
387
- if !@reconnection_delay.nil?
388
- SelfSDK.logger.info "websocket connection closed (#{event.code}) #{event.reason}"
389
- sleep @reconnection_delay
390
- SelfSDK.logger.info "reconnecting..."
391
- end
392
- @reconnection_delay = 3
393
- start_connection
394
- end
395
- end
408
+ @ws.start
396
409
  end
397
410
 
398
411
 
@@ -401,29 +414,32 @@ module SelfSDK
401
414
  data = event.data.pack('c*')
402
415
  hdr = SelfMsg::Header.new(data: data)
403
416
 
404
- SelfSDK.logger.info " - received #{hdr.id} (#{hdr.type})"
417
+ SelfSDK.logger.debug " - received #{hdr.id} (#{hdr.type})"
405
418
  case hdr.type
406
419
  when SelfMsg::MsgTypeMSG
407
- SelfSDK.logger.info "Message #{hdr.id} received"
420
+ SelfSDK.logger.debug "[#{hdr.id}] message received"
408
421
  m = SelfMsg::Message.new(data: data)
409
422
  process_incomming_message m
410
423
  when SelfMsg::MsgTypeACK
411
- SelfSDK.logger.info "#{hdr.id} acknowledged"
424
+ SelfSDK.logger.debug "[#{hdr.id}] acknowledged"
412
425
  mark_as_acknowledged hdr.id
413
426
  when SelfMsg::MsgTypeERR
414
427
  SelfSDK.logger.warn "error on #{hdr.id}"
415
428
  e = SelfMsg::Notification.new(data: data)
416
429
  SelfSDK.logger.warn "#{e.error}"
430
+ # TODO control @messages[hdr.id] being nil
431
+ raise "ERROR : #{e.error}" if @messages[hdr.id].nil?
432
+
417
433
  @messages[hdr.id][:response] = {error: e.error}
418
434
  mark_as_acknowledged(hdr.id)
419
435
  mark_as_arrived(hdr.id)
420
436
  when SelfMsg::MsgTypeACL
421
- SelfSDK.logger.info "ACL received"
437
+ SelfSDK.logger.debug "#{hdr.id} ACL received"
422
438
  a = SelfMsg::Acl.new(data: data)
423
439
  process_incomming_acl a
424
440
  end
425
441
  rescue TypeError
426
- SelfSDK.logger.info "invalid array message"
442
+ SelfSDK.logger.debug "invalid array message"
427
443
  end
428
444
 
429
445
  def process_incomming_acl(input)
@@ -433,8 +449,8 @@ module SelfSDK
433
449
  mark_as_arrived 'acl_list'
434
450
  rescue StandardError => e
435
451
  p "Error processing incoming ACL #{input.id} #{input.payload}"
436
- SelfSDK.logger.info e
437
- SelfSDK.logger.info e.backtrace
452
+ SelfSDK.logger.debug e
453
+ SelfSDK.logger.debug e.backtrace
438
454
  nil
439
455
  end
440
456
 
@@ -446,28 +462,35 @@ module SelfSDK
446
462
  @messages[message.id][:response] = message
447
463
  mark_as_arrived message.id
448
464
  else
449
- SelfSDK.logger.info "Received async message #{input.id}"
465
+ SelfSDK.logger.debug "Received async message #{input.id}"
450
466
  message.validate! @uuid_observer[message.id][:original_message] if @uuid_observer.include? message.id
467
+ SelfSDK.logger.debug "[#{input.id}] is valid, notifying observer"
451
468
  notify_observer(message)
452
469
  end
453
470
  rescue StandardError => e
454
471
  p "Error processing incoming message #{e.message}"
455
- SelfSDK.logger.info e
472
+ SelfSDK.logger.debug e
456
473
  # p e.backtrace
457
474
  nil
458
475
  end
459
476
 
460
477
  def parse_and_write_offset(input)
461
- SelfSDK::Messages.parse(input, self)
462
- ensure
478
+ msg = SelfSDK::Messages.parse(input, self)
463
479
  write_offset(input.offset)
480
+ # Avoid catching any other decryption errors.
481
+ msg
482
+ rescue SelfSDK::Messages::UnmappedMessage => e
483
+ # this is an ummapped message, let's ignore it but write the offset.
484
+ write_offset(input.offset)
485
+ nil
464
486
  end
465
487
 
466
488
  # Authenticates current client on the websocket server.
467
489
  def authenticate
468
490
  @auth_id = SecureRandom.uuid if @auth_id.nil?
491
+ @offset = read_offset
469
492
 
470
- SelfSDK.logger.info "authenticating"
493
+ SelfSDK.logger.debug "authenticating with offset (#{@offset})"
471
494
 
472
495
  a = SelfMsg::Auth.new
473
496
  a.id = @auth_id
@@ -475,15 +498,11 @@ module SelfSDK
475
498
  a.device = @device_id
476
499
  a.offset = @offset
477
500
 
478
- send_raw a
501
+ @ws.send a
479
502
 
480
503
  @auth_id = nil
481
504
  end
482
505
 
483
- def send_raw(msg)
484
- @ws.send(msg.to_fb.bytes)
485
- end
486
-
487
506
  # Marks a message as arrived.
488
507
  def mark_as_arrived(id)
489
508
  # Return if no one is waiting for this message
@@ -518,27 +537,8 @@ module SelfSDK
518
537
  f.flock(File::LOCK_EX)
519
538
  f.write(offset.to_s.rjust(19, "0"))
520
539
  end
521
- end
522
-
523
- def migrate_old_storage_format
524
- # Move the offset file
525
- old_offset_file = "#{@raw_storage_dir}/#{@jwt.id}:#{@device_id}.offset"
526
- if File.file?(old_offset_file)
527
- File.open(old_offset_file, 'rb') do |f|
528
- offset = f.read.unpack('q')[0]
529
- write_offset(offset)
530
- end
531
- File.delete(old_offset_file)
532
- end
533
-
534
- # Move all pickle files
535
- crypto_path = "#{@storage_dir}/keys/#{@jwt.key_id}"
536
- FileUtils.mkdir_p crypto_path unless File.exist? crypto_path
537
- Dir[File.join(@raw_storage_dir, "*.pickle")].each do |file|
538
- filename = File.basename(file, ".pickle")
539
- File.rename file, "#{crypto_path}/#{filename}.pickle"
540
- end
541
-
540
+ SelfSDK.logger.debug "offset written #{offset}"
541
+ @offset = offset
542
542
  end
543
543
 
544
544
  def select_priority(mtype)
data/lib/selfsdk.rb CHANGED
@@ -36,7 +36,7 @@ module SelfSDK
36
36
  BASE_URL = "https://api.joinself.com".freeze
37
37
  MESSAGING_URL = "wss://messaging.joinself.com/v2/messaging".freeze
38
38
 
39
- attr_reader :client
39
+ attr_reader :client, :started
40
40
  attr_accessor :messaging_client
41
41
 
42
42
  # Initializes a SelfSDK App
@@ -59,6 +59,7 @@ module SelfSDK
59
59
 
60
60
  @client = RestClient.new(base_url(opts), app_id, app_key, env)
61
61
  messaging_url = messaging_url(opts)
62
+ @started = false
62
63
  unless messaging_url.nil?
63
64
  @messaging_client = MessagingClient.new(messaging_url,
64
65
  @client,
@@ -69,6 +70,17 @@ module SelfSDK
69
70
  end
70
71
  end
71
72
 
73
+ # Starts the websockets connection and processes incoming messages in case the client
74
+ # is initialized with auto_start set to false.
75
+ def start
76
+ return self if @started
77
+
78
+ @messaging_client.start
79
+ @started = true
80
+
81
+ self
82
+ end
83
+
72
84
  # Provides access to SelfSDK::Services::Facts service
73
85
  def facts
74
86
  @facts ||= SelfSDK::Services::Facts.new(requester)
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selfsdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.209
4
+ version: 0.0.210
5
5
  platform: ruby
6
6
  authors:
7
- - Aldgate Ventures
7
+ - Self Group Ltd.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []