selfsdk 0.0.209 → 0.0.210

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 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: []