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 +4 -4
- data/lib/acl.rb +31 -18
- data/lib/client.rb +27 -15
- data/lib/crypto.rb +72 -25
- data/lib/messages/base.rb +1 -1
- data/lib/messages/message.rb +4 -1
- data/lib/messaging.rb +180 -180
- data/lib/selfsdk.rb +13 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97a576e450b5c015dd14fdac839bdffa8de9a3f6d5fdcda6f9c09970a9a11e63
|
4
|
+
data.tar.gz: 02eb35df5ad3fb4609ad5b91704c8085b74532434ca94c458d88fa1f35e62aa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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.
|
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
|
-
|
72
|
-
|
73
|
-
res = HTTParty.post("#{@self_url}#{endpoint}",
|
71
|
+
safe_request do
|
72
|
+
HTTParty.post("#{@self_url}#{endpoint}",
|
74
73
|
headers: {
|
75
|
-
|
76
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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("
|
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("
|
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
|
-
|
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
|
-
|
101
|
+
f = nil
|
102
|
+
::SelfSDK.logger.debug("- [crypto] decrypting a message")
|
76
103
|
session_file_name = session_path(sender, sender_device)
|
77
104
|
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
111
|
-
if
|
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(
|
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(
|
141
|
-
if
|
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(
|
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
|
data/lib/messages/message.rb
CHANGED
@@ -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
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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.
|
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.
|
239
|
+
SelfSDK.logger.debug "[#{m.id}] -> to #{m.recipient}"
|
158
240
|
send_message m
|
159
241
|
end
|
160
242
|
end
|
161
243
|
|
162
|
-
#
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
252
|
-
SelfSDK.logger.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
417
|
+
SelfSDK.logger.debug " - received #{hdr.id} (#{hdr.type})"
|
405
418
|
case hdr.type
|
406
419
|
when SelfMsg::MsgTypeMSG
|
407
|
-
SelfSDK.logger.
|
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.
|
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.
|
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.
|
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.
|
437
|
-
SelfSDK.logger.
|
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.
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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