selfsdk 0.0.217 → 0.0.219
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/client.rb +1 -1
- data/lib/crypto.rb +94 -113
- data/lib/messages/message.rb +1 -1
- data/lib/messaging.rb +14 -38
- data/lib/selfsdk.rb +4 -0
- data/lib/storage.rb +236 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 390f0ac2c3808caf3c347697209a0cea518844c1fc716412c76377715dff18b7
|
4
|
+
data.tar.gz: 8507869d086a970157451a51bafc1e3585e3f053c34c75ffcbf8752677f02c9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f212c0f88eeb44cfca786cb3a04e08748b63e7dba9dc56c356da598963c7f0917d37b70595925eaf31e15c35d1d7dff818caa08a789533cb6cc2a0303391141
|
7
|
+
data.tar.gz: adb386b6f42bea923763019ddad12310ff7719ff13c6191f6ed06d49a19f1f8aa111c62f1a5a9eff62e06fe13f1f551d1ddfc26830cd8e66b1a61f3cde2692df
|
data/lib/client.rb
CHANGED
data/lib/crypto.rb
CHANGED
@@ -1,37 +1,43 @@
|
|
1
1
|
# Copyright 2020 Self Group Ltd. All Rights Reserved.
|
2
2
|
|
3
3
|
require 'self_crypto'
|
4
|
+
require_relative 'storage'
|
4
5
|
|
5
6
|
module SelfSDK
|
6
7
|
class Crypto
|
7
|
-
def initialize(client, device,
|
8
|
+
def initialize(client, device, storage, storage_key)
|
8
9
|
@client = client
|
9
10
|
@device = device
|
10
11
|
@storage_key = storage_key
|
11
|
-
@storage_folder = "#{storage_folder}/#{@client.jwt.key_id}"
|
12
12
|
@lock_strategy = true
|
13
13
|
@mode = "r+"
|
14
|
+
@storage = storage
|
15
|
+
@keys = {}
|
16
|
+
@mutex = Mutex.new
|
17
|
+
|
18
|
+
@storage.tx do
|
19
|
+
::SelfSDK.logger.debug('Checking if account exists...')
|
20
|
+
if @storage.account_exists?
|
21
|
+
# 1a) if alice's account file exists load the pickle from the file
|
22
|
+
@account = SelfCrypto::Account.from_pickle(@storage.account_olm, @storage_key)
|
23
|
+
else
|
24
|
+
# 1b-i) if create a new account for alice if one doesn't exist already
|
25
|
+
@account = SelfCrypto::Account.from_seed(@client.jwt.key)
|
14
26
|
|
15
|
-
|
16
|
-
|
17
|
-
@account = SelfCrypto::Account.from_pickle(File.read(account_path), @storage_key)
|
18
|
-
else
|
19
|
-
# 1b-i) if create a new account for alice if one doesn't exist already
|
20
|
-
@account = SelfCrypto::Account.from_seed(@client.jwt.key)
|
21
|
-
|
22
|
-
# 1b-ii) generate some keys for alice and publish them
|
23
|
-
@account.gen_otk(100)
|
27
|
+
# 1b-ii) generate some keys for alice and publish them
|
28
|
+
@account.gen_otk(100)
|
24
29
|
|
25
|
-
|
26
|
-
|
30
|
+
# 1b-iii) convert those keys to json
|
31
|
+
@keys = @account.otk['curve25519'].map{|k,v| {id: k, key: v}}
|
32
|
+
keys = @keys.to_json
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
# 1b-iv) post those keys to POST /v1/identities/<selfid>/devices/1/pre_keys/
|
35
|
+
res = @client.post("/v1/apps/#{@client.jwt.id}/devices/#{@device}/pre_keys", keys)
|
36
|
+
raise 'unable to push prekeys, please try in a few minutes' if res.code != 200
|
31
37
|
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
# 1b-v) store the account to a file
|
39
|
+
@storage.account_create(@account.to_pickle(@storage_key))
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
@@ -43,119 +49,79 @@ module SelfSDK
|
|
43
49
|
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
44
50
|
|
45
51
|
sessions = {}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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])
|
52
|
+
ct = ""
|
53
|
+
tx do
|
54
|
+
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
55
|
+
recipients.each do |r|
|
56
|
+
sid = @storage.sid(r[:id], r[:device_id])
|
57
|
+
next if sid == @storage.app_id
|
58
|
+
|
59
|
+
session_with_bob = get_outbound_session_with_bob(@storage.session_get_olm(sid), r[:id], r[:device_id])
|
60
|
+
::SelfSDK.logger.debug("- [crypto] adding group participant #{r[:id]}:#{r[:device_id]}")
|
61
|
+
gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
|
62
|
+
sessions[sid] = session_with_bob
|
63
63
|
rescue => e
|
64
64
|
::SelfSDK.logger.warn("- [crypto] there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
|
65
65
|
::SelfSDK.logger.warn("- [crypto] #{e}")
|
66
66
|
next
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
# 5) encrypt a message
|
75
|
-
::SelfSDK.logger.debug("- [crypto] group encrypting message")
|
76
|
-
ct = gs.encrypt(message).to_s
|
69
|
+
# 5) encrypt a message
|
70
|
+
::SelfSDK.logger.debug("- [crypto] group encrypting message")
|
71
|
+
ct = gs.encrypt(message).to_s
|
77
72
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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)
|
73
|
+
# 6) store the session to a file
|
74
|
+
::SelfSDK.logger.debug("- [crypto] storing sessions")
|
75
|
+
sessions.each do |sid, session_with_bob|
|
76
|
+
pickle = session_with_bob.to_pickle(@storage_key)
|
77
|
+
@storage.session_update(sid, pickle)
|
88
78
|
end
|
89
79
|
end
|
90
|
-
|
91
80
|
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
|
99
81
|
end
|
100
82
|
|
101
|
-
def decrypt(message, sender, sender_device)
|
102
|
-
|
103
|
-
|
104
|
-
session_file_name = session_path(sender, sender_device)
|
83
|
+
def decrypt(message, sender, sender_device, offset)
|
84
|
+
::SelfSDK.logger.debug("- [crypto] decrypting a message #{message}")
|
85
|
+
sid = @storage.sid(sender, sender_device)
|
105
86
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
::SelfSDK.logger.debug("- [crypto] loading sessions")
|
113
|
-
session_with_bob = get_inbound_session_with_bob(f, message)
|
87
|
+
pt = ""
|
88
|
+
tx do
|
89
|
+
::SelfSDK.logger.debug("- [crypto] loading sessions for #{sid}")
|
90
|
+
::SelfSDK.logger.debug("- [crypto] #{@account.one_time_keys["curve25519"]}")
|
91
|
+
session_with_bob = get_inbound_session_with_bob(@storage.session_get_olm(sid), message)
|
114
92
|
|
115
|
-
|
116
|
-
|
117
|
-
|
93
|
+
# 8) create a group session and set the identity of the account you're using
|
94
|
+
::SelfSDK.logger.debug("- [crypto] create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
|
95
|
+
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
118
96
|
|
119
|
-
|
120
|
-
|
121
|
-
|
97
|
+
# 9) add all recipients and their sessions
|
98
|
+
::SelfSDK.logger.debug("- [crypto] add all recipients and their sessions #{sender}:#{sender_device}")
|
99
|
+
gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
|
122
100
|
|
123
|
-
|
124
|
-
|
125
|
-
|
101
|
+
# 10) decrypt the message ciphertext
|
102
|
+
::SelfSDK.logger.debug("- [crypto] decrypt the message ciphertext")
|
103
|
+
pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
|
126
104
|
|
127
|
-
|
128
|
-
|
105
|
+
# 11) store the session to a file
|
106
|
+
::SelfSDK.logger.debug("- [crypto] store the session to a file")
|
129
107
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
f.write(pickle)
|
134
|
-
f.truncate(f.pos)
|
135
|
-
else
|
136
|
-
File.write(session_file_name, pickle)
|
108
|
+
pickle = session_with_bob.to_pickle(@storage_key)
|
109
|
+
@storage.session_update(sid, pickle)
|
110
|
+
@storage.account_set_offset(offset)
|
137
111
|
end
|
138
|
-
|
139
112
|
pt
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
113
|
+
rescue => e
|
114
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: original message: #{message}")
|
115
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: exception message: #{e.message}")
|
116
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: exception backtrace: #{e.backtrace}")
|
117
|
+
@storage.account_set_offset(offset)
|
118
|
+
pt
|
144
119
|
end
|
145
120
|
|
146
121
|
private
|
147
122
|
|
148
|
-
def
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
def session_path(selfid, device)
|
153
|
-
"#{@storage_folder}/#{selfid}:#{device}-session.pickle"
|
154
|
-
end
|
155
|
-
|
156
|
-
def get_outbound_session_with_bob(f, recipient, recipient_device)
|
157
|
-
if !f.nil?
|
158
|
-
pickle = f.read
|
123
|
+
def get_outbound_session_with_bob(pickle, recipient, recipient_device)
|
124
|
+
if !pickle.nil?
|
159
125
|
# 2a) if bob's session file exists load the pickle from the file
|
160
126
|
session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
|
161
127
|
else
|
@@ -184,18 +150,24 @@ module SelfSDK
|
|
184
150
|
session_with_bob
|
185
151
|
end
|
186
152
|
|
187
|
-
def get_inbound_session_with_bob(
|
188
|
-
if !
|
189
|
-
pickle = f.read
|
153
|
+
def get_inbound_session_with_bob(pickle, message)
|
154
|
+
if !pickle.nil?
|
190
155
|
# 7a) if carol's session file exists load the pickle from the file
|
191
156
|
session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
|
192
157
|
end
|
193
158
|
|
159
|
+
::SelfSDK.logger.debug("- [crypto] pickle nil? #{pickle.nil?}")
|
160
|
+
::SelfSDK.logger.debug("- [crypto] getting one time message for #{@client.jwt.id}:#{@device}")
|
161
|
+
|
194
162
|
# 7b-i) if you have not previously sent or received a message to/from bob,
|
195
163
|
# you should extract the initial message from the group message intended
|
196
164
|
# for your account id.
|
197
165
|
m = SelfCrypto::GroupMessage.new(message.to_s).get_message("#{@client.jwt.id}:#{@device}")
|
198
166
|
|
167
|
+
::SelfSDK.logger.debug("- [crypto] one time message #{m}")
|
168
|
+
::SelfSDK.logger.debug("- [crypto] session is nil? #{session_with_bob.nil?}")
|
169
|
+
::SelfSDK.logger.debug("- [crypto] is prekey message? #{m.instance_of?(SelfCrypto::PreKeyMessage)}")
|
170
|
+
|
199
171
|
# if there is no session, create one
|
200
172
|
# if there is an existing session and we are sent a one time key message, check
|
201
173
|
# if it belongs to this current session and create a new inbound session if it doesn't
|
@@ -204,6 +176,7 @@ module SelfSDK
|
|
204
176
|
session_with_bob = @account.inbound_session(m)
|
205
177
|
|
206
178
|
# 7b-iii) remove the session's prekey from the account
|
179
|
+
::SelfSDK.logger.debug "- [crypto] removing one time keys for bob #{session_with_bob}"
|
207
180
|
@account.remove_one_time_keys(session_with_bob)
|
208
181
|
|
209
182
|
current_one_time_keys = @account.otk['curve25519']
|
@@ -221,12 +194,20 @@ module SelfSDK
|
|
221
194
|
res = @client.post("/v1/apps/#{@client.jwt.id}/devices/#{@device}/pre_keys", keys.to_json)
|
222
195
|
raise 'unable to push prekeys, please try in a few minutes' if res.code != 200
|
223
196
|
end
|
224
|
-
|
225
|
-
|
197
|
+
::SelfSDK.logger.debug "- [crypto] updating account with removed keys"
|
198
|
+
@storage.account_update(@account.to_pickle(@storage_key))
|
226
199
|
end
|
227
200
|
|
228
201
|
session_with_bob
|
229
202
|
end
|
230
203
|
|
204
|
+
def tx
|
205
|
+
@mutex.lock
|
206
|
+
@storage.tx do
|
207
|
+
yield
|
208
|
+
end
|
209
|
+
ensure
|
210
|
+
@mutex.unlock
|
211
|
+
end
|
231
212
|
end
|
232
213
|
end
|
data/lib/messages/message.rb
CHANGED
@@ -31,7 +31,7 @@ module SelfSDK
|
|
31
31
|
else
|
32
32
|
envelope = input
|
33
33
|
issuer = input.sender.split(":")
|
34
|
-
messaging.encryption_client.decrypt(input.ciphertext, issuer.first, issuer.last)
|
34
|
+
messaging.encryption_client.decrypt(input.ciphertext, issuer.first, issuer.last, input.offset)
|
35
35
|
end
|
36
36
|
|
37
37
|
jwt = JSON.parse(body, symbolize_names: true)
|
data/lib/messaging.rb
CHANGED
@@ -13,6 +13,7 @@ require_relative 'messages/message'
|
|
13
13
|
module SelfSDK
|
14
14
|
class WebsocketClient
|
15
15
|
ON_DEMAND_CLOSE_CODE=3999
|
16
|
+
CONNECTION_SUPERCEDED=1011
|
16
17
|
|
17
18
|
attr_accessor :ws
|
18
19
|
|
@@ -42,7 +43,7 @@ module SelfSDK
|
|
42
43
|
@ws.on :close do |event|
|
43
44
|
SelfSDK.logger.debug "connection closed detected : #{event.code} #{event.reason}"
|
44
45
|
|
45
|
-
if event.code
|
46
|
+
if not [ON_DEMAND_CLOSE_CODE, CONNECTION_SUPERCEDED].include? event.code
|
46
47
|
raise StandardError('websocket connection closed') if !@auto_reconnect
|
47
48
|
|
48
49
|
if !@reconnection_delay.nil?
|
@@ -59,6 +60,7 @@ module SelfSDK
|
|
59
60
|
|
60
61
|
# Sends a closing message to the websocket client.
|
61
62
|
def close
|
63
|
+
SelfSDK.logger.debug "connection closed by the client"
|
62
64
|
@ws.close(ON_DEMAND_CLOSE_CODE, "connection closed by the client")
|
63
65
|
end
|
64
66
|
|
@@ -80,7 +82,6 @@ module SelfSDK
|
|
80
82
|
class MessagingClient
|
81
83
|
DEFAULT_DEVICE="1"
|
82
84
|
DEFAULT_AUTO_RECONNECT=true
|
83
|
-
DEFAULT_STORAGE_DIR="./.self_storage"
|
84
85
|
|
85
86
|
PRIORITIES = {
|
86
87
|
'chat.invite': SelfSDK::Messages::PRIORITY_VISIBLE,
|
@@ -104,12 +105,12 @@ module SelfSDK
|
|
104
105
|
#
|
105
106
|
# @param url [string] self-messaging url
|
106
107
|
# @params client [Object] SelfSDK::Client object
|
107
|
-
# @option opts [string] :
|
108
|
+
# @option opts [string] :storage the library used to persist session data.
|
108
109
|
# @params storage_key [String] seed to encrypt messaging
|
109
110
|
# @params storage_folder [String] folder to perist messaging encryption
|
110
111
|
# @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
|
111
112
|
# @option opts [String] :device_id The device id to be used by the app defaults to "1".
|
112
|
-
def initialize(url, client, storage_key, options = {})
|
113
|
+
def initialize(url, client, storage_key, storage, options = {})
|
113
114
|
@mon = Monitor.new
|
114
115
|
@messages = {}
|
115
116
|
@acks = {}
|
@@ -121,18 +122,13 @@ module SelfSDK
|
|
121
122
|
@timeout = 120 # seconds
|
122
123
|
@auth_id = SecureRandom.uuid
|
123
124
|
@device_id = options.fetch(:device_id, DEFAULT_DEVICE)
|
124
|
-
@raw_storage_dir = options.fetch(:storage_dir, DEFAULT_STORAGE_DIR)
|
125
|
-
@storage_dir = "#{@raw_storage_dir}/apps/#{@jwt.id}/devices/#{@device_id}"
|
126
|
-
FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
|
127
|
-
@offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
|
128
|
-
@offset = read_offset
|
129
125
|
@source = SelfSDK::Sources.new("#{__dir__}/sources.json")
|
126
|
+
@storage = storage
|
130
127
|
|
131
128
|
unless options.include? :no_crypto
|
132
|
-
|
133
|
-
FileUtils.mkdir_p crypto_path unless File.exist? crypto_path
|
134
|
-
@encryption_client = Crypto.new(@client, @device_id, crypto_path, storage_key)
|
129
|
+
@encryption_client = Crypto.new(@client, @device_id, @storage, storage_key)
|
135
130
|
end
|
131
|
+
@offset = @storage.account_offset
|
136
132
|
|
137
133
|
@ws = if options.include? :ws
|
138
134
|
options[:ws]
|
@@ -352,7 +348,7 @@ module SelfSDK
|
|
352
348
|
|
353
349
|
SelfSDK.logger.debug " - notifying by type"
|
354
350
|
SelfSDK.logger.debug " - #{message.typ}"
|
355
|
-
SelfSDK.logger.debug " - #{message}"
|
351
|
+
SelfSDK.logger.debug " - #{message.payload}"
|
356
352
|
SelfSDK.logger.debug " - #{@type_observer.keys.join(',')}"
|
357
353
|
|
358
354
|
# Return if there is no observer setup for this kind of message
|
@@ -371,6 +367,7 @@ module SelfSDK
|
|
371
367
|
|
372
368
|
def subscribe(type, &block)
|
373
369
|
type = @source.message_type(type) if type.is_a? Symbol
|
370
|
+
SelfSDK.logger.debug "Subscribing to messages by type: #{type}"
|
374
371
|
@type_observer[type] = { block: block }
|
375
372
|
end
|
376
373
|
|
@@ -431,7 +428,7 @@ module SelfSDK
|
|
431
428
|
end
|
432
429
|
|
433
430
|
def process_incomming_message(input)
|
434
|
-
message =
|
431
|
+
message = parse(input)
|
435
432
|
|
436
433
|
if @messages.include? message.id
|
437
434
|
message.validate! @messages[message.id][:original_message]
|
@@ -450,21 +447,17 @@ module SelfSDK
|
|
450
447
|
nil
|
451
448
|
end
|
452
449
|
|
453
|
-
def
|
450
|
+
def parse(input)
|
454
451
|
msg = SelfSDK::Messages.parse(input, self)
|
455
|
-
write_offset(input.offset)
|
456
|
-
# Avoid catching any other decryption errors.
|
457
452
|
msg
|
458
|
-
rescue SelfSDK::Messages::UnmappedMessage
|
459
|
-
# this is an ummapped message, let's ignore it but write the offset.
|
460
|
-
write_offset(input.offset)
|
453
|
+
rescue SelfSDK::Messages::UnmappedMessage
|
461
454
|
nil
|
462
455
|
end
|
463
456
|
|
464
457
|
# Authenticates current client on the websocket server.
|
465
458
|
def authenticate
|
466
459
|
@auth_id = SecureRandom.uuid if @auth_id.nil?
|
467
|
-
@offset =
|
460
|
+
@offset = @storage.account_offset
|
468
461
|
|
469
462
|
SelfSDK.logger.debug "authenticating with offset (#{@offset})"
|
470
463
|
|
@@ -500,23 +493,6 @@ module SelfSDK
|
|
500
493
|
end
|
501
494
|
end
|
502
495
|
|
503
|
-
def read_offset
|
504
|
-
return 0 unless File.exist? @offset_file
|
505
|
-
|
506
|
-
File.open(@offset_file, 'rb') do |f|
|
507
|
-
return f.read.to_i
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
def write_offset(offset)
|
512
|
-
File.open(@offset_file, 'wb') do |f|
|
513
|
-
f.flock(File::LOCK_EX)
|
514
|
-
f.write(offset.to_s.rjust(19, "0"))
|
515
|
-
end
|
516
|
-
SelfSDK.logger.debug "offset written #{offset}"
|
517
|
-
@offset = offset
|
518
|
-
end
|
519
|
-
|
520
496
|
def select_priority(mtype)
|
521
497
|
PRIORITIES[mtype] || SelfSDK::Messages::PRIORITY_VISIBLE
|
522
498
|
end
|
data/lib/selfsdk.rb
CHANGED
@@ -49,6 +49,7 @@ module SelfSDK
|
|
49
49
|
# @option opts [String] :messaging_url The messaging self provider url.
|
50
50
|
# @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
|
51
51
|
# @option opts [Symbol] :env The environment to be used, defaults to ":production".
|
52
|
+
# @option opts [String] :storage the library used to persist session data.
|
52
53
|
def initialize(app_id, app_key, storage_key, storage_dir, opts = {})
|
53
54
|
app_key = cleanup_key(app_key)
|
54
55
|
|
@@ -60,9 +61,12 @@ module SelfSDK
|
|
60
61
|
messaging_url = messaging_url(opts)
|
61
62
|
@started = false
|
62
63
|
unless messaging_url.nil?
|
64
|
+
device_id = opts.fetch(:device_id, MessagingClient::DEFAULT_DEVICE)
|
65
|
+
storage = opts.fetch(:storage, SelfSDK::Storage.new(@client.jwt.id, device_id, storage_dir, storage_key))
|
63
66
|
@messaging_client = MessagingClient.new(messaging_url,
|
64
67
|
@client,
|
65
68
|
storage_key,
|
69
|
+
storage,
|
66
70
|
storage_dir: storage_dir,
|
67
71
|
auto_reconnect: opts.fetch(:auto_reconnect, MessagingClient::DEFAULT_AUTO_RECONNECT),
|
68
72
|
device_id: opts.fetch(:device_id, MessagingClient::DEFAULT_DEVICE))
|
data/lib/storage.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# Copyright 2020 Self Group Ltd. All Rights Reserved.
|
2
|
+
require 'sqlite3'
|
3
|
+
|
4
|
+
module SelfSDK
|
5
|
+
class Storage
|
6
|
+
attr_accessor :app_id
|
7
|
+
|
8
|
+
def initialize(app_id, app_device, storage_folder, _key_id)
|
9
|
+
@app_id = sid(app_id, app_device)
|
10
|
+
|
11
|
+
# Create the storage folder if it does not exist
|
12
|
+
create_directory_skel("#{storage_folder}/identities/#{app_id}/devices/#{app_device}")
|
13
|
+
|
14
|
+
# Create the database
|
15
|
+
@db = SQLite3::Database.new(File.join("#{storage_folder}/identities/#{app_id}/devices/#{app_device}/", 'self.db'))
|
16
|
+
set_pragmas
|
17
|
+
create_accounts_table
|
18
|
+
create_sessions_table
|
19
|
+
m = StorageMigrator.new(@db, "#{storage_folder}/apps", @app_id)
|
20
|
+
m.migrate
|
21
|
+
end
|
22
|
+
|
23
|
+
def tx
|
24
|
+
@db.transaction
|
25
|
+
yield
|
26
|
+
@db.commit
|
27
|
+
rescue SQLite3::Exception => e
|
28
|
+
@db.rollback
|
29
|
+
rescue => e
|
30
|
+
@db.rollback
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
|
34
|
+
def account_exists?
|
35
|
+
row = @db.execute("SELECT olm_account FROM accounts WHERE as_identifier = ?", [@app_id.encode("UTF-8")]).first
|
36
|
+
!row.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def account_create(olm)
|
40
|
+
@db.execute("INSERT INTO accounts (as_identifier, offset, olm_account) VALUES (?, ?, ?);", [ @app_id.encode("UTF-8"), 0, olm.encode("UTF-8") ])
|
41
|
+
rescue
|
42
|
+
end
|
43
|
+
|
44
|
+
def account_update(olm)
|
45
|
+
@db.execute("UPDATE accounts SET olm_account = ? WHERE as_identifier = ?", [ olm.encode("UTF-8"), @app_id.encode("UTF-8") ])
|
46
|
+
end
|
47
|
+
|
48
|
+
def account_olm
|
49
|
+
row = @db.execute("SELECT olm_account FROM accounts WHERE as_identifier = ?;", [@app_id.encode("UTF-8")]).first
|
50
|
+
return nil unless row
|
51
|
+
|
52
|
+
row.first
|
53
|
+
end
|
54
|
+
|
55
|
+
def account_offset
|
56
|
+
row = @db.execute("SELECT offset FROM accounts WHERE as_identifier = ?;", @app_id.encode("UTF-8")).first
|
57
|
+
return nil unless row
|
58
|
+
|
59
|
+
row.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def account_set_offset(offset)
|
63
|
+
@db.execute("UPDATE accounts SET offset = ? WHERE as_identifier = ?;", [ offset, @app_id.encode("UTF-8") ])
|
64
|
+
end
|
65
|
+
|
66
|
+
def session_create(sid, olm)
|
67
|
+
@db.execute("INSERT INTO sessions (as_identifier, with_identifier, olm_session) VALUES (?, ?, ?);", [ @app_id.encode("UTF-8"), sid.encode("UTF-8"), olm.encode("UTF-8") ])
|
68
|
+
end
|
69
|
+
|
70
|
+
def session_update(sid, olm)
|
71
|
+
row = @db.execute("SELECT olm_session FROM sessions WHERE as_identifier = ? AND with_identifier = ?", [@app_id.encode("UTF-8"), sid.encode("UTF-8")]).first
|
72
|
+
if row.nil?
|
73
|
+
session_create(sid, olm)
|
74
|
+
else
|
75
|
+
@db.execute("UPDATE sessions SET olm_session = ? WHERE as_identifier = ? AND with_identifier = ?;", [ olm.encode("UTF-8"), @app_id.encode("UTF-8"), sid.encode("UTF-8") ])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def session_get_olm(sid)
|
80
|
+
row = @db.execute("SELECT olm_session FROM sessions WHERE as_identifier = ? AND with_identifier = ?", [@app_id.encode("UTF-8"), sid.encode("UTF-8")]).first
|
81
|
+
return nil if row.nil?
|
82
|
+
|
83
|
+
row.first
|
84
|
+
end
|
85
|
+
|
86
|
+
def sid(selfid, device)
|
87
|
+
"#{selfid}:#{device}"
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Create a folder if it does not exist
|
93
|
+
def create_directory_skel(storage_folder)
|
94
|
+
FileUtils.mkdir_p storage_folder unless File.exist? storage_folder
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
raise ERR_INVALID_DIRECTORY
|
97
|
+
rescue
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_pragmas
|
101
|
+
pragma_statement = <<~SQL
|
102
|
+
PRAGMA synchronous = NORMAL;
|
103
|
+
PRAGMA journal_mode = WAL;
|
104
|
+
PRAGMA temp_store = MEMORY;
|
105
|
+
SQL
|
106
|
+
|
107
|
+
@db.execute_batch(pragma_statement)
|
108
|
+
rescue SQLite3::Exception => e
|
109
|
+
puts "Exception occurred: #{e}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_accounts_table
|
113
|
+
accounts_table_statement = <<~SQL
|
114
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
115
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
116
|
+
as_identifier TEXT NOT NULL,
|
117
|
+
offset INTEGER NOT NULL,
|
118
|
+
olm_account BLOB NOT NULL
|
119
|
+
);
|
120
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_as_identifier
|
121
|
+
ON accounts (as_identifier);
|
122
|
+
SQL
|
123
|
+
|
124
|
+
@db.execute_batch(accounts_table_statement)
|
125
|
+
rescue SQLite3::Exception => e
|
126
|
+
puts "Exception occurred: #{e}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_sessions_table
|
130
|
+
# TODO we could deduplicate as_identifier and with_identifier here
|
131
|
+
# by creating a record for each on a new identifier table,
|
132
|
+
# but this is only temporary
|
133
|
+
session_table_statement = <<~SQL
|
134
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
136
|
+
as_identifier TEXT NOT NULL,
|
137
|
+
with_identifier TEXT NOT NULL,
|
138
|
+
olm_session BLOB NOT NULL
|
139
|
+
);
|
140
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_with_identifier
|
141
|
+
ON sessions (as_identifier, with_identifier);
|
142
|
+
SQL
|
143
|
+
|
144
|
+
@db.execute_batch(session_table_statement)
|
145
|
+
rescue SQLite3::Exception => e
|
146
|
+
puts "Exception occurred: #{e}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class StorageMigrator
|
151
|
+
def initialize(db, storage_folder, app_id)
|
152
|
+
@db = db
|
153
|
+
# Old versions of the sdk using that same storage folder shouldn't be affected in any way
|
154
|
+
|
155
|
+
@base_path = "#{storage_folder}/#{app_id.split(':').first}"
|
156
|
+
@app_id = app_id
|
157
|
+
end
|
158
|
+
|
159
|
+
def migrate
|
160
|
+
return unless File.exist?(@base_path)
|
161
|
+
|
162
|
+
# Parse the account information.
|
163
|
+
accounts = parse_accounts
|
164
|
+
|
165
|
+
persist_accounts(accounts)
|
166
|
+
|
167
|
+
# Depreciate the base path.
|
168
|
+
File.rename("#{@base_path}", "#{@base_path}-depreciated")
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def parse_accounts
|
174
|
+
accounts = {}
|
175
|
+
|
176
|
+
Dir.glob(File.join(@base_path, "**/*")).each do |path|
|
177
|
+
if File.directory?(path)
|
178
|
+
next
|
179
|
+
end
|
180
|
+
|
181
|
+
case File.extname(path)
|
182
|
+
when ".offset"
|
183
|
+
file_name = File.basename(path, ".offset")
|
184
|
+
offset = File.read(path)[0, 19].to_i
|
185
|
+
|
186
|
+
accounts[file_name] = {} unless accounts.key? file_name
|
187
|
+
accounts[file_name][:offset] = offset
|
188
|
+
when ".pickle"
|
189
|
+
file_name = File.basename(path, ".pickle")
|
190
|
+
content = File.read(path)
|
191
|
+
|
192
|
+
accounts[@app_id] = {} unless accounts.key? @app_id
|
193
|
+
if file_name == "account"
|
194
|
+
accounts[@app_id][:account] = content
|
195
|
+
else
|
196
|
+
if accounts.key? @app_id
|
197
|
+
accounts[@app_id][:sessions] = [] unless accounts[@app_id].key? :sessions
|
198
|
+
accounts[@app_id][:sessions] << {
|
199
|
+
with: file_name.sub("-session", ""),
|
200
|
+
session: content
|
201
|
+
}
|
202
|
+
next
|
203
|
+
end
|
204
|
+
accounts[@app_id][:account] = content
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
accounts
|
210
|
+
end
|
211
|
+
|
212
|
+
def persist_accounts(accounts)
|
213
|
+
@db.transaction
|
214
|
+
accounts.each do |inbox_id, account|
|
215
|
+
@db.execute(
|
216
|
+
"INSERT INTO accounts (as_identifier, offset, olm_account) VALUES ($1, $2, $3)",
|
217
|
+
[inbox_id, account[:offset], account[:account]]
|
218
|
+
)
|
219
|
+
|
220
|
+
account[:sessions].each do |session|
|
221
|
+
@db.execute(
|
222
|
+
"INSERT INTO sessions (as_identifier, with_identifier, olm_session) VALUES ($1, $2, $3)",
|
223
|
+
[inbox_id, session[:with], session[:session]]
|
224
|
+
)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
@db.commit
|
229
|
+
rescue SQLite3::Exception => e
|
230
|
+
puts "Exception occurred"
|
231
|
+
puts e
|
232
|
+
puts e.backtrace
|
233
|
+
@db.rollback
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selfsdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.219
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Self Group Ltd.
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: sqlite3
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
181
195
|
- !ruby/object:Gem::Dependency
|
182
196
|
name: bundler
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -395,6 +409,7 @@ files:
|
|
395
409
|
- lib/signature_graph.rb
|
396
410
|
- lib/source_definition.rb
|
397
411
|
- lib/sources.rb
|
412
|
+
- lib/storage.rb
|
398
413
|
homepage: https://www.joinself.com/
|
399
414
|
licenses:
|
400
415
|
- MIT
|