selfsdk 0.0.209 → 0.0.211

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: 2a1963ba30a0e3805ba038837fc8c4cbdabea43463faf7682ae7562b0aae230d
4
+ data.tar.gz: 128af12164985e2cfd838b6d00e847be098739ce2a67152f000b34cf700e73ea
5
5
  SHA512:
6
- metadata.gz: a916f8c596495f580ee4b29c8e253325be6409133bb5eafb7df398b8848aef5148acc44cee6302d3ad25a1136c9e2344add606290ad49f2bc0be7a081b83b939
7
- data.tar.gz: 51790af7cb7fb53f911c7484584deb733f4d55b9d151b23a4086cab356946cf95c066449a307dd20be755ea33a0db0d287596bb247414446de89f2d3b8866e9e
6
+ metadata.gz: 39ecffbd49ffb189b63754779d8057876171abe70311b5603e10391ef5240b3f591e6a54dbfce76cab3bef46d19f0be2907542d1f649a2d9a32cfe3d287beede
7
+ data.tar.gz: f6dbe1b68052d3f7eb17983d5ec6bdf966e9d87644c503855402f9cf841a3c92906e29825f2d3192374f24e81858763943947170a909a0b370fa275a3aa50b27
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
@@ -28,73 +30,117 @@ module SelfSDK
28
30
  raise 'unable to push prekeys, please try in a few minutes' if res.code != 200
29
31
 
30
32
  # 1b-v) store the account to a file
33
+ FileUtils.mkdir_p(@storage_folder)
31
34
  File.write(account_path, @account.to_pickle(storage_key))
32
35
  end
33
36
  end
34
37
 
35
38
  def encrypt(message, recipients)
36
- ::SelfSDK.logger.debug('encrypting a message')
39
+ ::SelfSDK.logger.debug('- [crypto] encrypting a message')
37
40
 
38
41
  # 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')
42
+ ::SelfSDK.logger.debug('- [crypto] create a group session and set the identity of the account youre using')
40
43
  gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
41
44
 
42
45
  sessions = {}
43
- ::SelfSDK.logger.debug('managing sessions with all recipients')
46
+ locks = {}
47
+ ::SelfSDK.logger.debug('- [crypto] managing sessions with all recipients')
48
+
44
49
  recipients.each do |r|
50
+ f = nil
51
+ next if r[:id] == @client.jwt.id && r[:device_id] == @device
52
+
45
53
  session_file_name = session_path(r[:id], r[:device_id])
46
54
  session_with_bob = nil
47
55
 
48
56
  begin
49
- session_with_bob = get_outbound_session_with_bob(r[:id], r[:device_id], session_file_name)
57
+ if File.exist?(session_file_name)
58
+ # Lock the session file
59
+ locks[session_file_name] = File.open(session_file_name, @mode)
60
+ locks[session_file_name].flock(File::LOCK_EX)
61
+ end
62
+ session_with_bob = get_outbound_session_with_bob(locks[session_file_name], r[:id], r[:device_id])
50
63
  rescue => e
51
- ::SelfSDK.logger.warn(" there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
52
- ::SelfSDK.logger.warn(e)
64
+ ::SelfSDK.logger.warn("- [crypto] there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
65
+ ::SelfSDK.logger.warn("- [crypto] #{e}")
53
66
  next
54
67
  end
55
68
 
56
- ::SelfSDK.logger.debug(" adding group participant #{r[:id]}:#{r[:device_id]}")
69
+ ::SelfSDK.logger.debug("- [crypto] adding group participant #{r[:id]}:#{r[:device_id]}")
57
70
  gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
58
71
  sessions[session_file_name] = session_with_bob
59
72
  end
60
73
 
61
74
  # 5) encrypt a message
62
- ::SelfSDK.logger.debug("group encrypting message")
75
+ ::SelfSDK.logger.debug("- [crypto] group encrypting message")
63
76
  ct = gs.encrypt(message).to_s
64
77
 
65
78
  # 6) store the session to a file
66
- ::SelfSDK.logger.debug("storing sessions")
79
+ ::SelfSDK.logger.debug("- [crypto] storing sessions")
67
80
  sessions.each do |session_file_name, session_with_bob|
68
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
81
+ pickle = session_with_bob.to_pickle(@storage_key)
82
+ if locks[session_file_name]
83
+ locks[session_file_name].rewind
84
+ locks[session_file_name].write(pickle)
85
+ locks[session_file_name].truncate(locks[session_file_name].pos)
86
+ else
87
+ File.write(session_file_name, pickle)
88
+ end
69
89
  end
70
90
 
71
91
  ct
92
+ ensure
93
+ locks.each do |session_file_name, lock|
94
+ # Unlock the file
95
+ if lock
96
+ lock.flock(File::LOCK_UN)
97
+ end
98
+ end
72
99
  end
73
100
 
74
101
  def decrypt(message, sender, sender_device)
75
- ::SelfSDK.logger.debug("decrypting a message")
102
+ f = nil
103
+ ::SelfSDK.logger.debug("- [crypto] decrypting a message")
76
104
  session_file_name = session_path(sender, sender_device)
77
105
 
78
- ::SelfSDK.logger.debug("loading sessions")
79
- session_with_bob = get_inbound_session_with_bob(message, session_file_name)
106
+ if File.exist?(session_file_name)
107
+ # Lock the session file
108
+ f = File.open(session_file_name, @mode)
109
+ f.flock(File::LOCK_EX)
110
+ end
111
+
112
+ ::SelfSDK.logger.debug("- [crypto] loading sessions")
113
+ session_with_bob = get_inbound_session_with_bob(f, message)
80
114
 
81
115
  # 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}")
116
+ ::SelfSDK.logger.debug("- [crypto] create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
83
117
  gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
84
118
 
85
119
  # 9) add all recipients and their sessions
86
- ::SelfSDK.logger.debug("add all recipients and their sessions #{@sender}:#{@sender_device}")
120
+ ::SelfSDK.logger.debug("- [crypto] add all recipients and their sessions #{@sender}:#{@sender_device}")
87
121
  gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
88
122
 
89
123
  # 10) decrypt the message ciphertext
90
- ::SelfSDK.logger.debug("decrypt the message ciphertext")
124
+ ::SelfSDK.logger.debug("- [crypto] decrypt the message ciphertext")
91
125
  pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
92
126
 
93
127
  # 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))
128
+ ::SelfSDK.logger.debug("- [crypto] store the session to a file")
129
+
130
+ pickle = session_with_bob.to_pickle(@storage_key)
131
+ if !f.nil?
132
+ f.rewind
133
+ f.write(pickle)
134
+ f.truncate(f.pos)
135
+ else
136
+ File.write(session_file_name, pickle)
137
+ end
96
138
 
97
139
  pt
140
+ ensure
141
+ # Unlock the session file
142
+ f&.flock(File::LOCK_UN)
143
+ f&.close
98
144
  end
99
145
 
100
146
  private
@@ -107,10 +153,11 @@ module SelfSDK
107
153
  "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
108
154
  end
109
155
 
110
- def get_outbound_session_with_bob(recipient, recipient_device, session_file_name)
111
- if File.exist?(session_file_name)
156
+ def get_outbound_session_with_bob(f, recipient, recipient_device)
157
+ if !f.nil?
158
+ pickle = f.read
112
159
  # 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)
160
+ session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
114
161
  else
115
162
  # 2b-i) if you have not previously sent or recevied a message to/from bob,
116
163
  # you must get his identity key from GET /v1/identities/bob/
@@ -121,7 +168,7 @@ module SelfSDK
121
168
 
122
169
  if res.code != 200
123
170
  b = JSON.parse(res.body)
124
- ::SelfSDK.logger.error "identity response : #{b['message']}"
171
+ ::SelfSDK.logger.error "- [crypto] identity response : #{b['message']}"
125
172
  raise "could not get identity pre_keys"
126
173
  end
127
174
 
@@ -137,10 +184,11 @@ module SelfSDK
137
184
  session_with_bob
138
185
  end
139
186
 
140
- def get_inbound_session_with_bob(message, session_file_name)
141
- if File.exist?(session_file_name)
187
+ def get_inbound_session_with_bob(f, message)
188
+ if !f.nil?
189
+ pickle = f.read
142
190
  # 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)
191
+ session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
144
192
  end
145
193
 
146
194
  # 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,90 @@ 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
+ @ws.ping 'ping'
71
+ end
72
+
73
+ def send(message)
74
+ @ws.send(message.to_fb.bytes)
75
+ end
76
+ end
77
+
14
78
  class MessagingClient
15
79
  DEFAULT_DEVICE="1"
16
80
  DEFAULT_AUTO_RECONNECT=true
17
81
  DEFAULT_STORAGE_DIR="./.self_storage"
18
- ON_DEMAND_CLOSE_CODE=3999
19
82
 
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
- }
83
+ PRIORITIES = {
84
+ 'chat.invite': SelfSDK::Messages::PRIORITY_VISIBLE,
85
+ 'chat.join': SelfSDK::Messages::PRIORITY_INVISIBLE,
86
+ 'chat.message': SelfSDK::Messages::PRIORITY_VISIBLE,
87
+ 'chat.message.delete': SelfSDK::Messages::PRIORITY_INVISIBLE,
88
+ 'chat.message.delivered': SelfSDK::Messages::PRIORITY_INVISIBLE,
89
+ 'chat.message.edit': SelfSDK::Messages::PRIORITY_INVISIBLE,
90
+ 'chat.message.read': SelfSDK::Messages::PRIORITY_INVISIBLE,
91
+ 'chat.remove': SelfSDK::Messages::PRIORITY_INVISIBLE,
92
+ 'document.sign.req': SelfSDK::Messages::PRIORITY_VISIBLE,
93
+ 'identities.authenticate.req': SelfSDK::Messages::PRIORITY_VISIBLE,
94
+ 'identities.connections.req': SelfSDK::Messages::PRIORITY_VISIBLE,
95
+ 'identities.facts.query.req': SelfSDK::Messages::PRIORITY_VISIBLE,
96
+ 'identities.facts.issue': SelfSDK::Messages::PRIORITY_VISIBLE,
97
+ 'identities.notify': SelfSDK::Messages::PRIORITY_VISIBLE }.freeze
36
98
 
37
99
  attr_accessor :client, :jwt, :device_id, :ack_timeout, :timeout, :type_observer, :uuid_observer, :encryption_client, :source
38
100
 
@@ -47,7 +109,6 @@ module SelfSDK
47
109
  # @option opts [String] :device_id The device id to be used by the app defaults to "1".
48
110
  def initialize(url, client, storage_key, options = {})
49
111
  @mon = Monitor.new
50
- @url = url
51
112
  @messages = {}
52
113
  @acks = {}
53
114
  @type_observer = {}
@@ -58,14 +119,12 @@ module SelfSDK
58
119
  @timeout = 120 # seconds
59
120
  @auth_id = SecureRandom.uuid
60
121
  @device_id = options.fetch(:device_id, DEFAULT_DEVICE)
61
- @auto_reconnect = options.fetch(:auto_reconnect, DEFAULT_AUTO_RECONNECT)
62
122
  @raw_storage_dir = options.fetch(:storage_dir, DEFAULT_STORAGE_DIR)
63
123
  @storage_dir = "#{@raw_storage_dir}/apps/#{@jwt.id}/devices/#{@device_id}"
64
124
  FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
65
125
  @offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
66
126
  @offset = read_offset
67
127
  @source = SelfSDK::Sources.new("#{__dir__}/sources.json")
68
- migrate_old_storage_format
69
128
 
70
129
  unless options.include? :no_crypto
71
130
  crypto_path = "#{@storage_dir}/keys"
@@ -73,13 +132,55 @@ module SelfSDK
73
132
  @encryption_client = Crypto.new(@client, @device_id, crypto_path, storage_key)
74
133
  end
75
134
 
76
- if options.include? :ws
77
- @ws = options[:ws]
78
- else
79
- start
135
+ @ws = if options.include? :ws
136
+ options[:ws]
137
+ else
138
+ WebsocketClient.new(url,
139
+ options.fetch(:auto_reconnect, DEFAULT_AUTO_RECONNECT),
140
+ -> { authenticate },
141
+ ->(event) { on_message(event) })
142
+ end
143
+ end
144
+
145
+ # Starts the underlying websocket connection.
146
+ def start
147
+ SelfSDK.logger.debug "starting"
148
+ auth_id = @auth_id.dup
149
+
150
+ @mon.synchronize do
151
+ @acks[auth_id] = { waiting_cond: @mon.new_cond,
152
+ waiting: true,
153
+ timeout: SelfSDK::Time.now + @ack_timeout }
154
+ end
155
+
156
+ Thread.new do
157
+ EM.run start_connection
80
158
  end
159
+
160
+ Thread.new do
161
+ loop do
162
+ sleep 10
163
+ clean_timeouts
164
+ @ws.ping
165
+ end
166
+ end
167
+
168
+ @mon.synchronize do
169
+ @acks[auth_id][:waiting_cond].wait_while { @acks[auth_id][:waiting] }
170
+ @acks.delete(auth_id)
171
+ end
172
+
173
+ return unless @acks.include? auth_id
174
+
175
+ # In case this does not succeed start the process again.
176
+ if @acks[auth_id][:waiting]
177
+ close
178
+ start_connection
179
+ end
180
+ @acks.delete(auth_id)
81
181
  end
82
182
 
183
+ # Stops the underlying websocket connection.
83
184
  def stop
84
185
  @acks.each do |k, _v|
85
186
  mark_as_acknowledged(k)
@@ -91,26 +192,10 @@ module SelfSDK
91
192
  end
92
193
 
93
194
  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
195
+ @ws.close
112
196
  end
113
197
 
198
+ # Checks if the session with a specified identity / device is already created.
114
199
  def session?(identifier, device)
115
200
  path = @encryption_client.session_path(identifier, device)
116
201
  File.file?(path)
@@ -136,7 +221,7 @@ module SelfSDK
136
221
  end
137
222
  end
138
223
 
139
- SelfSDK.logger.info "sending custom message #{request_body.to_json}"
224
+ SelfSDK.logger.debug "sending custom message #{request_body.to_json}"
140
225
  current_device = "#{@jwt.id}:#{@device_id}"
141
226
 
142
227
  recs.each do |r|
@@ -154,43 +239,19 @@ module SelfSDK
154
239
  m.message_type = r[:typ]
155
240
  m.priority = select_priority(r[:typ])
156
241
 
157
- SelfSDK.logger.info " -> to #{m.recipient}"
242
+ SelfSDK.logger.debug "[#{m.id}] -> to #{m.recipient}"
158
243
  send_message m
159
244
  end
160
245
  end
161
246
 
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
247
+ # Sends a command to list ACL rules.
187
248
  def list_acl_rules
188
249
  wait_for 'acl_list' do
189
250
  a = SelfMsg::Acl.new
190
251
  a.id = SecureRandom.uuid
191
252
  a.command = SelfMsg::AclCommandLIST
192
253
 
193
- send_raw a
254
+ @ws.send a
194
255
  end
195
256
  end
196
257
 
@@ -198,7 +259,7 @@ module SelfSDK
198
259
  #
199
260
  # @params msg [SelfMsg::Message] message object to be sent
200
261
  def send_and_wait_for_response(msgs, original)
201
- SelfSDK.logger.info "sending/wait for #{msgs.first.id}"
262
+ SelfSDK.logger.debug "sending/wait for #{msgs.first.id}"
202
263
  wait_for msgs.first.id, original do
203
264
  msgs.each do |msg|
204
265
  send_message msg
@@ -211,7 +272,7 @@ module SelfSDK
211
272
  #
212
273
  # @params uuid [string] unique identifier for a conversation
213
274
  def wait_for(uuid, msg = nil)
214
- SelfSDK.logger.info "sending #{uuid}"
275
+ SelfSDK.logger.debug "sending #{uuid}"
215
276
  @mon.synchronize do
216
277
  @messages[uuid] = {
217
278
  waiting_cond: @mon.new_cond,
@@ -223,14 +284,14 @@ module SelfSDK
223
284
 
224
285
  yield
225
286
 
226
- SelfSDK.logger.info "waiting for client to respond #{uuid}"
287
+ SelfSDK.logger.debug "waiting for client to respond #{uuid}"
227
288
  @mon.synchronize do
228
289
  @messages[uuid][:waiting_cond].wait_while do
229
290
  @messages[uuid][:waiting]
230
291
  end
231
292
  end
232
293
 
233
- SelfSDK.logger.info "response received for #{uuid}"
294
+ SelfSDK.logger.debug "response received for #{uuid}"
234
295
  @messages[uuid][:response]
235
296
  ensure
236
297
  @messages.delete(uuid)
@@ -248,14 +309,21 @@ module SelfSDK
248
309
  timeout: SelfSDK::Time.now + @ack_timeout,
249
310
  }
250
311
  end
251
- send_raw(msg)
252
- SelfSDK.logger.info "waiting for acknowledgement #{uuid}"
312
+ @ws.send msg
313
+ SelfSDK.logger.debug "waiting for acknowledgement #{uuid}"
253
314
  @mon.synchronize do
254
315
  @acks[uuid][:waiting_cond].wait_while do
255
316
  @acks[uuid][:waiting]
256
317
  end
257
318
  end
258
- SelfSDK.logger.info "acknowledged #{uuid}"
319
+
320
+ # response has timed out
321
+ if @acks[uuid][:timed_out]
322
+ SelfSDK.logger.debug "acknowledgement response timed out re-sending message #{uuid}"
323
+ return send_message(msg)
324
+ end
325
+
326
+ SelfSDK.logger.debug "acknowledged #{uuid}"
259
327
  true
260
328
  ensure
261
329
  @acks.delete(uuid)
@@ -281,6 +349,7 @@ module SelfSDK
281
349
  # Notify the type observer for the given message
282
350
  def notify_observer(message)
283
351
  if @uuid_observer.include? message.id
352
+ SelfSDK.logger.debug " - notifying by id"
284
353
  observer = @uuid_observer[message.id]
285
354
  message.validate!(observer[:original_message]) if observer.include?(:original_message)
286
355
  Thread.new do
@@ -290,9 +359,15 @@ module SelfSDK
290
359
  return
291
360
  end
292
361
 
362
+ SelfSDK.logger.debug " - notifying by type"
363
+ SelfSDK.logger.debug " - #{message.typ}"
364
+ SelfSDK.logger.debug " - #{message}"
365
+ SelfSDK.logger.debug " - #{@type_observer.keys.join(',')}"
366
+
293
367
  # Return if there is no observer setup for this kind of message
294
368
  return unless @type_observer.include? message.typ
295
369
 
370
+ SelfSDK.logger.debug " - notifying by type (Y)"
296
371
  Thread.new do
297
372
  @type_observer[message.typ][:block].call(message)
298
373
  end
@@ -310,38 +385,6 @@ module SelfSDK
310
385
 
311
386
  private
312
387
 
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
388
 
346
389
  # Cleans expired messages
347
390
  def clean_timeouts
@@ -355,44 +398,17 @@ module SelfSDK
355
398
  next unless list[uuid][:timeout] < SelfSDK::Time.now
356
399
 
357
400
  @mon.synchronize do
358
- SelfSDK.logger.info "message response timed out #{uuid}"
401
+ SelfSDK.logger.debug "[#{uuid}] message response timed out"
359
402
  list[uuid][:waiting] = false
360
403
  list[uuid][:waiting_cond].broadcast
404
+ list[uuid][:timed_out] = true
361
405
  end
362
406
  end
363
407
  end
364
408
 
365
409
  # Creates a websocket connection and sets up its callbacks.
366
410
  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
411
+ @ws.start
396
412
  end
397
413
 
398
414
 
@@ -401,29 +417,32 @@ module SelfSDK
401
417
  data = event.data.pack('c*')
402
418
  hdr = SelfMsg::Header.new(data: data)
403
419
 
404
- SelfSDK.logger.info " - received #{hdr.id} (#{hdr.type})"
420
+ SelfSDK.logger.debug " - received #{hdr.id} (#{hdr.type})"
405
421
  case hdr.type
406
422
  when SelfMsg::MsgTypeMSG
407
- SelfSDK.logger.info "Message #{hdr.id} received"
423
+ SelfSDK.logger.debug "[#{hdr.id}] message received"
408
424
  m = SelfMsg::Message.new(data: data)
409
425
  process_incomming_message m
410
426
  when SelfMsg::MsgTypeACK
411
- SelfSDK.logger.info "#{hdr.id} acknowledged"
427
+ SelfSDK.logger.debug "[#{hdr.id}] acknowledged"
412
428
  mark_as_acknowledged hdr.id
413
429
  when SelfMsg::MsgTypeERR
414
430
  SelfSDK.logger.warn "error on #{hdr.id}"
415
431
  e = SelfMsg::Notification.new(data: data)
416
432
  SelfSDK.logger.warn "#{e.error}"
433
+ # TODO control @messages[hdr.id] being nil
434
+ raise "ERROR : #{e.error}" if @messages[hdr.id].nil?
435
+
417
436
  @messages[hdr.id][:response] = {error: e.error}
418
437
  mark_as_acknowledged(hdr.id)
419
438
  mark_as_arrived(hdr.id)
420
439
  when SelfMsg::MsgTypeACL
421
- SelfSDK.logger.info "ACL received"
440
+ SelfSDK.logger.debug "#{hdr.id} ACL received"
422
441
  a = SelfMsg::Acl.new(data: data)
423
442
  process_incomming_acl a
424
443
  end
425
444
  rescue TypeError
426
- SelfSDK.logger.info "invalid array message"
445
+ SelfSDK.logger.debug "invalid array message"
427
446
  end
428
447
 
429
448
  def process_incomming_acl(input)
@@ -433,8 +452,8 @@ module SelfSDK
433
452
  mark_as_arrived 'acl_list'
434
453
  rescue StandardError => e
435
454
  p "Error processing incoming ACL #{input.id} #{input.payload}"
436
- SelfSDK.logger.info e
437
- SelfSDK.logger.info e.backtrace
455
+ SelfSDK.logger.debug e
456
+ SelfSDK.logger.debug e.backtrace
438
457
  nil
439
458
  end
440
459
 
@@ -446,28 +465,35 @@ module SelfSDK
446
465
  @messages[message.id][:response] = message
447
466
  mark_as_arrived message.id
448
467
  else
449
- SelfSDK.logger.info "Received async message #{input.id}"
468
+ SelfSDK.logger.debug "Received async message #{input.id}"
450
469
  message.validate! @uuid_observer[message.id][:original_message] if @uuid_observer.include? message.id
470
+ SelfSDK.logger.debug "[#{input.id}] is valid, notifying observer"
451
471
  notify_observer(message)
452
472
  end
453
473
  rescue StandardError => e
454
474
  p "Error processing incoming message #{e.message}"
455
- SelfSDK.logger.info e
475
+ SelfSDK.logger.debug e
456
476
  # p e.backtrace
457
477
  nil
458
478
  end
459
479
 
460
480
  def parse_and_write_offset(input)
461
- SelfSDK::Messages.parse(input, self)
462
- ensure
481
+ msg = SelfSDK::Messages.parse(input, self)
463
482
  write_offset(input.offset)
483
+ # Avoid catching any other decryption errors.
484
+ msg
485
+ rescue SelfSDK::Messages::UnmappedMessage => e
486
+ # this is an ummapped message, let's ignore it but write the offset.
487
+ write_offset(input.offset)
488
+ nil
464
489
  end
465
490
 
466
491
  # Authenticates current client on the websocket server.
467
492
  def authenticate
468
493
  @auth_id = SecureRandom.uuid if @auth_id.nil?
494
+ @offset = read_offset
469
495
 
470
- SelfSDK.logger.info "authenticating"
496
+ SelfSDK.logger.debug "authenticating with offset (#{@offset})"
471
497
 
472
498
  a = SelfMsg::Auth.new
473
499
  a.id = @auth_id
@@ -475,15 +501,11 @@ module SelfSDK
475
501
  a.device = @device_id
476
502
  a.offset = @offset
477
503
 
478
- send_raw a
504
+ @ws.send a
479
505
 
480
506
  @auth_id = nil
481
507
  end
482
508
 
483
- def send_raw(msg)
484
- @ws.send(msg.to_fb.bytes)
485
- end
486
-
487
509
  # Marks a message as arrived.
488
510
  def mark_as_arrived(id)
489
511
  # Return if no one is waiting for this message
@@ -518,27 +540,8 @@ module SelfSDK
518
540
  f.flock(File::LOCK_EX)
519
541
  f.write(offset.to_s.rjust(19, "0"))
520
542
  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
-
543
+ SelfSDK.logger.debug "offset written #{offset}"
544
+ @offset = offset
542
545
  end
543
546
 
544
547
  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.211
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: []