selfsdk 0.0.179 → 0.0.180

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: f79c82e418ad27688b7d9f0f2635937a69f7c805a71611a02529a8d10ced9709
4
- data.tar.gz: aa77a30f37b6a71acf1aeed6a77112ca08505f91598b462962fb68f45e483c31
3
+ metadata.gz: ad25868d7199141b1efdfaaa9a6dd3e54584ba9bcd3422859f6e1ea0bb04524c
4
+ data.tar.gz: e656e160c2256e13067554873bc77995fd1939b954d7883dee59c6c139bbcb90
5
5
  SHA512:
6
- metadata.gz: ef6bb03cab8de8e44af7fad1f19a623345c6cd6fe00a4c1e0f35ab526e6b6523e2d8cd668c66d17cd85b5cf254f31a9e0dad4bbfc9c0a4301c3d67a044910d2f
7
- data.tar.gz: e7d3ce5591ea749537a6a2ee580b240b7033a6b4f6f2ba8fa3d64d11814d722844862e40a06c495a447bda01888be2f9b4e085fedb5534a5a89a75c7cfa4e965
6
+ metadata.gz: 9a7267604b80afb16bcf17952b9e4f073f0990e1816b926015600eb1ba026cc66de82adadd21c062e6a6d19a2a3a18167c39cd5a1df250c3f5dcdc0df1b84147
7
+ data.tar.gz: 516b19be9dd2ef9d083f26e9b1e80a9c006861228ed6658a7a03aae6aae0de2ad74f7018ddc1c1fe6f4ba525147b5f9a1bffb66e869e6a34ad6407eb7b554e2c
@@ -0,0 +1,97 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+ require 'open-uri'
5
+
6
+ module SelfSDK
7
+ module Chat
8
+ class FileObject
9
+ attr_accessor :link, :mime, :content, :key, :nonce, :ciphertext
10
+
11
+ def initialize(token, url)
12
+ @token = token
13
+ @url = url
14
+ end
15
+
16
+ def build_from_data(name, data, mime)
17
+ @key = SelfCrypto::Util.aead_xchacha20poly1305_ietf_keygen
18
+ @nonce = SelfCrypto::Util.aead_xchacha20poly1305_ietf_nonce
19
+ @content = data
20
+ @name = name
21
+ @mime = mime
22
+
23
+ # encrypt the object
24
+ @ciphertext = SelfCrypto::Util.aead_xchacha20poly1305_ietf_encrypt(@key, @nonce, @content)
25
+
26
+ # Upload
27
+ remote_object = upload(ciphertext)
28
+ @link = "#{@url}/v1/objects/#{remote_object["id"]}"
29
+ @expires = remote_object["expires"]
30
+
31
+ self
32
+ end
33
+
34
+ # Incoming objects
35
+ def build_from_object(input)
36
+ # Download from CDN
37
+ ciphertext = URI.open(input[:link], "Authorization" => "Bearer #{@token}").read
38
+
39
+ @content = ciphertext
40
+ @key = nil
41
+ @nonce = nil
42
+ if input.key?(:key) && !input[:key].empty?
43
+ # Decrypt
44
+ composed_key = extract_key(input[:key])
45
+ @key = composed_key[:key]
46
+ @nonce = composed_key[:nonce]
47
+
48
+ @content = SelfCrypto::Util.aead_xchacha20poly1305_ietf_decrypt(@key, @nonce, ciphertext)
49
+ end
50
+
51
+ @name = input[:name]
52
+ @link = input[:link]
53
+ @mime = input[:mime]
54
+ @expires = input[:expires]
55
+
56
+ self
57
+ end
58
+
59
+ def to_payload
60
+ {
61
+ name: @name,
62
+ link: @link,
63
+ key: build_key(@key, @nonce),
64
+ mime: @mime,
65
+ expires: @expires
66
+ }
67
+ end
68
+
69
+ def save(path)
70
+ File.open(path, 'wb') { |file| file.write(@content) }
71
+ end
72
+
73
+ private
74
+
75
+ def upload(ciphertext)
76
+ uri = URI.parse("#{@url}/v1/objects")
77
+ https = Net::HTTP.new(uri.host, uri.port)
78
+ https.use_ssl = true
79
+ req = Net::HTTP::Post.new(uri.path)
80
+ req["Authorization"] = "Bearer #{@token}"
81
+ req.body = ciphertext.force_encoding("UTF-8")
82
+ res = https.request(req)
83
+ JSON.parse(res.body)
84
+ end
85
+
86
+ def build_key(key, nonce)
87
+ Base64.urlsafe_encode64("#{key}#{nonce}", padding: false)
88
+ end
89
+
90
+ def extract_key(shareable_key)
91
+ k = Base64.urlsafe_decode64(shareable_key)
92
+ { key: k[0, 32],
93
+ nonce: k[32, (k.length - 32)] }
94
+ end
95
+ end
96
+ end
97
+ end
data/lib/chat/group.rb ADDED
@@ -0,0 +1,51 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module SelfSDK
6
+ module Chat
7
+ class Group
8
+ attr_accessor :gid, :name, :members, :payload
9
+
10
+ def initialize(chat, payload)
11
+ @chat = chat
12
+ @payload = payload
13
+ @gid = payload[:gid]
14
+ @members = payload[:members]
15
+ @name = payload[:name]
16
+ @link = payload[:link] if payload.key? :link
17
+ @key = payload[:key] if payload.key? :key
18
+ @mime = payload[:mime] if payload.key? :mime
19
+ end
20
+
21
+ # Sends an invitation to the specified user to join
22
+ # the group.
23
+ #
24
+ # @param user [String] user to be invited.
25
+ def invite(user)
26
+ raise "invalid input" if user.empty?
27
+
28
+ @members << user
29
+ @chat.invite(@gid, @name, @members)
30
+ end
31
+
32
+ # Sends a message to leave the current group.
33
+ def leave
34
+ @chat.leave(@gid, @members)
35
+ end
36
+
37
+ # Sends a confi¡rmation message that has joined the group.
38
+ def join
39
+ @chat.join(@gid, @members)
40
+ end
41
+
42
+ # Sends a message to the current group
43
+ #
44
+ # @param body [String] message body to be sent.
45
+ def message(body, opts = {})
46
+ opts[:gid] = @gid
47
+ @chat.message(@members, body, opts)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module SelfSDK
6
+ module Chat
7
+ class Message
8
+ attr_accessor :gid, :body, :from, :payload, :recipients, :objects
9
+
10
+ def initialize(chat, recipients, payload, auth_token, self_url)
11
+ @chat = chat
12
+ @recipients = recipients
13
+ @recipients = [@recipients] if @recipients.is_a? String
14
+ @gid = payload[:gid] if payload.key? :gid
15
+ @payload = payload
16
+ @payload[:jti] = SecureRandom.uuid unless @payload.include?(:jti)
17
+ @body = @payload[:msg]
18
+ @from = @payload[:iss]
19
+ return unless @payload.key?(:objects)
20
+
21
+ @objects = []
22
+ @payload[:objects].each do |o|
23
+ @objects << if o.key? :link
24
+ SelfSDK::Chat::FileObject.new(auth_token, self_url).build_from_object(o)
25
+ else
26
+ SelfSDK::Chat::FileObject.new(auth_token, self_url).build_from_data(o[:name], o[:data], o[:mime])
27
+ end
28
+ end
29
+ @payload[:objects] = []
30
+ @payload[:objects] = payload[:raw_objects] if payload[:raw_objects]
31
+ @objects.each do |o|
32
+ @payload[:objects] << o.to_payload
33
+ end
34
+ end
35
+
36
+ # delete! deletes the current message from the conversation.
37
+ def delete!
38
+ @chat.delete(@recipients, @payload[:jti], @payload[:gid])
39
+ end
40
+
41
+ # edit changes the current message body for all participants.
42
+ #
43
+ # @param body [string] the new message body.
44
+ def edit(body)
45
+ return if @recipients == [@chat.app_id]
46
+
47
+ @body = body
48
+ @chat.edit(@recipients, @payload[:jti], body, @payload[:gid])
49
+ end
50
+
51
+ # mark_as_delivered marks the current message as delivered if
52
+ # it comes from another recipient.
53
+ def mark_as_delivered
54
+ return if @recipients != [@chat.app_id]
55
+
56
+ @chat.delivered(@payload[:iss], @payload[:jti], @payload[:gid])
57
+ end
58
+
59
+ # mark_as_read marks the current message as read if it comes from
60
+ # another recipient.
61
+ def mark_as_read
62
+ return if @recipients != [@chat.app_id]
63
+
64
+ @chat.read(@payload[:iss], @payload[:jti], @payload[:gid])
65
+ end
66
+
67
+ # respond sends a direct response to the current message.
68
+ #
69
+ # @param body [string] the new message body.
70
+ #
71
+ # @return ChatMessage
72
+ def respond(body)
73
+ opts = {}
74
+ opts[:aud] = @payload[:gid] if @payload.key? :gid
75
+ opts[:gid] = @payload[:gid] if @payload.key? :gid
76
+ opts[:rid] = @payload[:jti]
77
+
78
+ to = @recipients
79
+ to = [@payload[:iss]] if @recipients == [@chat.app_id]
80
+
81
+ @chat.message(to, body, opts)
82
+ end
83
+
84
+ # message sends a new message to the same conversation as the current message.
85
+ #
86
+ # @param body [string] the new message body.
87
+ #
88
+ # @return ChatMessage
89
+ def message(body, opts = {})
90
+ opts[:aud] = @payload[:gid] if @payload.key? :gid
91
+ opts[:gid] = @payload[:gid] if @payload.key? :gid
92
+
93
+ to = opts[:recipients] if opts.key? :recipients
94
+ to = [@payload[:iss]] if @recipients == [@chat.app_id]
95
+
96
+ @chat.message(to, body, opts)
97
+ end
98
+
99
+ end
100
+ end
101
+ end
data/lib/crypto.rb CHANGED
@@ -32,9 +32,82 @@ module SelfSDK
32
32
  end
33
33
  end
34
34
 
35
- def encrypt(message, recipient, recipient_device)
36
- session_file_name = session_path(recipient, recipient_device)
35
+ def encrypt(message, recipients)
36
+ ::SelfSDK.logger.debug('encrypting a message')
37
37
 
38
+ # 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')
40
+ gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
41
+
42
+ sessions = {}
43
+ ::SelfSDK.logger.debug('managing sessions with all recipients')
44
+ recipients.each do |r|
45
+ session_file_name = session_path(r[:id], r[:device_id])
46
+ session_with_bob = nil
47
+
48
+ begin
49
+ session_with_bob = get_outbound_session_with_bob(r[:id], r[:device_id], session_file_name)
50
+ rescue
51
+ ::SelfSDK.logger.warn(" there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
52
+ ::SelfSDK.logger.warn(error)
53
+ next
54
+ end
55
+
56
+ ::SelfSDK.logger.debug(" adding group participant #{r[:id]}:#{r[:device_id]}")
57
+ gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
58
+ sessions[session_file_name] = session_with_bob
59
+ end
60
+
61
+ # 5) encrypt a message
62
+ ::SelfSDK.logger.debug("group encrypting message")
63
+ ct = gs.encrypt(message).to_s
64
+
65
+ # 6) store the session to a file
66
+ ::SelfSDK.logger.debug("storing sessions")
67
+ sessions.each do |session_file_name, session_with_bob|
68
+ File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
69
+ end
70
+
71
+ ct
72
+ end
73
+
74
+ def decrypt(message, sender, sender_device)
75
+ ::SelfSDK.logger.debug("decrypting a message")
76
+ session_file_name = session_path(sender, sender_device)
77
+
78
+ ::SelfSDK.logger.debug("loading sessions")
79
+ session_with_bob = get_inbound_session_with_bob(message, session_file_name)
80
+
81
+ # 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}")
83
+ gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
84
+
85
+ # 9) add all recipients and their sessions
86
+ ::SelfSDK.logger.debug("add all recipients and their sessions #{@sender}:#{@sender_device}")
87
+ gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
88
+
89
+ # 10) decrypt the message ciphertext
90
+ ::SelfSDK.logger.debug("decrypt the message ciphertext")
91
+ pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
92
+
93
+ # 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))
96
+
97
+ pt
98
+ end
99
+
100
+ private
101
+
102
+ def account_path
103
+ "#{@storage_folder}/account.pickle"
104
+ end
105
+
106
+ def session_path(selfid, device)
107
+ "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
108
+ end
109
+
110
+ def get_outbound_session_with_bob(recipient, recipient_device, session_file_name)
38
111
  if File.exist?(session_file_name)
39
112
  # 2a) if bob's session file exists load the pickle from the file
40
113
  session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
@@ -61,24 +134,10 @@ module SelfSDK
61
134
  session_with_bob = @account.outbound_session(curve25519_identity_key, one_time_key)
62
135
  end
63
136
 
64
- # 3) create a group session and set the identity of the account youre using
65
- gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
66
-
67
- # 4) add all recipients and their sessions
68
- gs.add_participant("#{recipient}:#{recipient_device}", session_with_bob)
69
-
70
- # 5) encrypt a message
71
- ct = gs.encrypt(message).to_s
72
-
73
- # 6) store the session to a file
74
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
75
-
76
- ct
137
+ session_with_bob
77
138
  end
78
139
 
79
- def decrypt(message, sender, sender_device)
80
- session_file_name = session_path(sender, sender_device)
81
-
140
+ def get_inbound_session_with_bob(message, session_file_name)
82
141
  if File.exist?(session_file_name)
83
142
  # 7a) if carol's session file exists load the pickle from the file
84
143
  session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
@@ -113,29 +172,8 @@ module SelfSDK
113
172
  File.write(account_path, @account.to_pickle(@storage_key))
114
173
  end
115
174
 
116
- # 8) create a group session and set the identity of the account you're using
117
- gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
118
-
119
- # 9) add all recipients and their sessions
120
- gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
121
-
122
- # 10) decrypt the message ciphertext
123
- pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
124
-
125
- # 11) store the session to a file
126
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
127
-
128
- pt
175
+ session_with_bob
129
176
  end
130
177
 
131
- private
132
-
133
- def account_path
134
- "#{@storage_folder}/account.pickle"
135
- end
136
-
137
- def session_path(selfid, device)
138
- "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
139
- end
140
178
  end
141
179
  end
@@ -43,10 +43,10 @@ module SelfSDK
43
43
 
44
44
  def proto(to_device)
45
45
  m = SelfMsg::Message.new
46
- m.id = SecureRandom.uuid
46
+ m.id = @id
47
47
  m.sender = "#{@jwt.id}:#{@messaging.device_id}"
48
48
  m.recipient = "#{@to}:#{to_device}"
49
- m.ciphertext = encrypt_message(@jwt.prepare(body), @to, to_device)
49
+ m.ciphertext = encrypt_message(@jwt.prepare(body), [{ id: @to, device_id: to_device }])
50
50
  m
51
51
  end
52
52
 
data/lib/messages/base.rb CHANGED
@@ -10,6 +10,7 @@ module SelfSDK
10
10
  :description, :sub, :exp_timeout
11
11
 
12
12
  def initialize(messaging)
13
+ @intermediary = nil
13
14
  @client = messaging.client
14
15
  @jwt = @client.jwt
15
16
  @messaging = messaging
@@ -44,8 +45,8 @@ module SelfSDK
44
45
  res.first
45
46
  end
46
47
 
47
- def encrypt_message(message, recipient, recipient_device)
48
- @messaging.encryption_client.encrypt(message, recipient, recipient_device)
48
+ def encrypt_message(message, recipients)
49
+ @messaging.encryption_client.encrypt(message, recipients)
49
50
  end
50
51
 
51
52
  def unauthorized?
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatInvite < Chat
10
+ MSG_TYPE = "chat.invite"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatJoin < Chat
10
+ MSG_TYPE = "chat.join"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatMessage < Chat
10
+ MSG_TYPE = "chat.message"
11
+ DEFAULT_EXP_TIMEOUT = 900
12
+
13
+ attr_accessor :body
14
+
15
+ def parse(input, envelope=nil)
16
+ super
17
+ @typ = MSG_TYPE
18
+ @body = @payload[:msg]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_msgproto'
6
+ require_relative 'chat'
7
+ require_relative '../ntptime'
8
+
9
+ module SelfSDK
10
+ module Messages
11
+ class ChatMessageDelivered < Chat
12
+ MSG_TYPE = "chat.message.delivered"
13
+ DEFAULT_EXP_TIMEOUT = 900
14
+
15
+ def parse(input, envelope=nil)
16
+ super
17
+ @typ = MSG_TYPE
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatMessageRead < Chat
10
+ MSG_TYPE = "chat.message.read"
11
+ DEFAULT_EXP_TIMEOUT = 900
12
+
13
+ def parse(input, envelope=nil)
14
+ super
15
+ @typ = MSG_TYPE
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatRemove < Chat
10
+ MSG_TYPE = "chat.remove"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -98,14 +98,14 @@ module SelfSDK
98
98
  @to_device = to_device
99
99
  if @intermediary.nil?
100
100
  recipient = "#{@to}:#{@to_device}"
101
- ciphertext = encrypt_message(@jwt.prepare(body), @to, @to_device)
101
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @to, device_id: @to_device}])
102
102
  else
103
103
  recipient = "#{@intermediary}:#{@to_device}"
104
- ciphertext = encrypt_message(@jwt.prepare(body), @intermediary, @to_device)
104
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @intermediary, device_id: @to_device}])
105
105
  end
106
106
 
107
107
  m = SelfMsg::Message.new
108
- m.id = SecureRandom.uuid
108
+ m.id = @id
109
109
  m.sender = "#{@jwt.id}:#{@messaging.device_id}"
110
110
  m.recipient = recipient
111
111
  m.ciphertext = ciphertext
@@ -91,7 +91,7 @@ module SelfSDK
91
91
  id: SecureRandom.uuid,
92
92
  sender: "#{@jwt.id}:#{@messaging.device_id}",
93
93
  recipient: "#{@to}:#{@to_device}",
94
- ciphertext: encrypt_message(@jwt.prepare(body), @to, @to_device)
94
+ ciphertext: encrypt_message(@jwt.prepare(body), [{id: @to, device_id: @to_device}])
95
95
  )
96
96
  end
97
97
  end
@@ -6,6 +6,12 @@ require_relative "fact_request"
6
6
  require_relative "fact_response"
7
7
  require_relative "authentication_resp"
8
8
  require_relative "authentication_req"
9
+ require_relative "chat_message"
10
+ require_relative "chat_message_read"
11
+ require_relative "chat_message_delivered"
12
+ require_relative "chat_invite"
13
+ require_relative "chat_join"
14
+ require_relative "chat_remove"
9
15
 
10
16
  module SelfSDK
11
17
  module Messages
@@ -35,8 +41,26 @@ module SelfSDK
35
41
  when "identities.authenticate.req"
36
42
  m = AuthenticationReq.new(messaging)
37
43
  m.parse(body, envelope)
44
+ when SelfSDK::Messages::ChatMessage::MSG_TYPE
45
+ m = ChatMessage.new(messaging)
46
+ m.parse(body, envelope)
47
+ when SelfSDK::Messages::ChatMessageDelivered::MSG_TYPE
48
+ m = ChatMessageDelivered.new(messaging)
49
+ m.parse(body, envelope)
50
+ when SelfSDK::Messages::ChatMessageRead::MSG_TYPE
51
+ m = ChatMessageRead.new(messaging)
52
+ m.parse(body, envelope)
53
+ when SelfSDK::Messages::ChatInvite::MSG_TYPE
54
+ m = ChatInvite.new(messaging)
55
+ m.parse(body, envelope)
56
+ when SelfSDK::Messages::ChatRemove::MSG_TYPE
57
+ m = ChatRemove.new(messaging)
58
+ m.parse(body, envelope)
59
+ when SelfSDK::Messages::ChatJoin::MSG_TYPE
60
+ m = ChatJoin.new(messaging)
61
+ m.parse(body, envelope)
38
62
  else
39
- raise StandardError.new("Invalid message type.")
63
+ raise StandardError.new("Invalid message type #{payload[:typ]}.")
40
64
  end
41
65
  return m
42
66
  end
data/lib/messaging.rb CHANGED
@@ -86,6 +86,7 @@ module SelfSDK
86
86
  m.id = SecureRandom.uuid
87
87
  m.sender = "#{@jwt.id}:#{@device_id}"
88
88
  m.recipient = "#{recipient}:#{recipient_device}"
89
+ # TODO: this is unencrypted!!!
89
90
  m.ciphertext = @jwt.prepare(request)
90
91
 
91
92
  send_message m
@@ -96,28 +97,40 @@ module SelfSDK
96
97
  # @param recipient [string] selfID to be requested
97
98
  # @param type [string] message type
98
99
  # @param request [hash] original message requesing information
99
- def send_custom(recipient, request_body)
100
- @client.devices(recipient).each do |to_device|
100
+ def send_custom(recipients, request_body)
101
+ # convert argument into an array if is a string
102
+ recipients = [recipients] if recipients.is_a? String
103
+
104
+ # send to current identity devices except the current one.
105
+ recipients |= [@jwt.id]
106
+
107
+ # build recipients list
108
+ recs = []
109
+ recipients.each do |r|
110
+ @client.devices(r).each do |to_device|
111
+ recs << { id: r, device_id: to_device }
112
+ end
113
+ end
114
+
115
+ SelfSDK.logger.info "sending custom message #{request_body.to_json}"
116
+ current_device = "#{@jwt.id}:#{@device_id}"
117
+
118
+ recs.each do |r|
119
+ next if current_device == "#{r[:id]}:#{r[:device_id]}"
120
+
121
+ request_body[:sub] = r[:id]
122
+ request_body[:aud] = r[:id] unless request_body.key?(:aud)
123
+ ciphertext = @encryption_client.encrypt(@jwt.prepare(request_body), recs)
124
+
101
125
  m = SelfMsg::Message.new
102
126
  m.id = SecureRandom.uuid
103
- m.sender = "#{@jwt.id}:#{@device_id}"
104
- m.recipient = "#{recipient}:#{to_device}"
105
- m.ciphertext = @jwt.prepare(request_body)
127
+ m.sender = current_device
128
+ m.recipient = "#{r[:id]}:#{r[:device_id]}"
129
+ m.ciphertext = ciphertext
106
130
 
131
+ SelfSDK.logger.info " -> to #{m.recipient}"
107
132
  send_message m
108
133
  end
109
-
110
- @client.devices(@jwt.id).each do |to_device|
111
- if to_device != @device_id
112
- m = SelfMsg::Message.new
113
- m.id = SecureRandom.uuid
114
- m.sender = "#{@jwt.id}:#{@device_id}"
115
- m.recipient = "#{recipient}:#{to_device}"
116
- m.ciphertext = @jwt.prepare(request_body)
117
-
118
- send_message m
119
- end
120
- end
121
134
  end
122
135
 
123
136
  # Allows incomming messages from the given identity
@@ -159,6 +172,7 @@ module SelfSDK
159
172
  #
160
173
  # @params msg [SelfMsg::Message] message object to be sent
161
174
  def send_and_wait_for_response(msgs, original)
175
+ SelfSDK.logger.info "sending/wait for #{msgs.first.id}"
162
176
  wait_for msgs.first.id, original do
163
177
  msgs.each do |msg|
164
178
  send_message msg
@@ -414,7 +428,7 @@ module SelfSDK
414
428
  rescue StandardError => e
415
429
  p "Error processing incoming message #{input.to_json}"
416
430
  SelfSDK.logger.info e
417
- p e.backtrace
431
+ # p e.backtrace
418
432
  nil
419
433
  end
420
434
 
data/lib/selfsdk.rb CHANGED
@@ -20,6 +20,7 @@ require_relative 'services/auth'
20
20
  require_relative 'services/facts'
21
21
  require_relative 'services/identity'
22
22
  require_relative 'services/messaging'
23
+ require_relative 'services/chat'
23
24
 
24
25
  # Namespace for classes and modules that handle Self interactions.
25
26
  module SelfSDK
@@ -82,6 +83,11 @@ module SelfSDK
82
83
  @messaging ||= SelfSDK::Services::Messaging.new(@messaging_client)
83
84
  end
84
85
 
86
+ # Provides access to SelfSDK::Services::Chat service
87
+ def chat
88
+ @chat ||= SelfSDK::Services::Chat.new(messaging, @client)
89
+ end
90
+
85
91
  def app_id
86
92
  client.jwt.id
87
93
  end
@@ -0,0 +1,178 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_crypto'
6
+ require_relative '../chat/file_object'
7
+ require_relative '../chat/group'
8
+ require_relative '../chat/message'
9
+ module SelfSDK
10
+ module Services
11
+ class Chat
12
+ attr_accessor :app_id
13
+
14
+ def initialize(messaging, client)
15
+ @messaging = messaging
16
+ @client = client
17
+ @app_id = @client.jwt.id
18
+ end
19
+
20
+ # Sends a message to a list of recipients.
21
+ #
22
+ # @param recipients [array] list of recipients to send the message to.
23
+ # @param body [string] the message content to be sent
24
+ def message(recipients, body, opts = {})
25
+ payload = {
26
+ typ: "chat.message",
27
+ msg: body,
28
+ }
29
+ payload[:jti] = opts[:jti] if opts.key? :jti
30
+ payload[:aud] = opts[:gid] if opts.key? :gid
31
+ payload[:gid] = opts[:gid] if opts.key? :gid
32
+ payload[:rid] = opts[:rid] if opts.key? :rid
33
+ payload[:objects] = opts[:objects] if opts.key? :objects
34
+
35
+ m = SelfSDK::Chat::Message.new(self, recipients, payload, @messaging.client.jwt.auth_token, @client.self_url)
36
+ _req = send(m.recipients, m.payload)
37
+
38
+ m
39
+ end
40
+
41
+ # Subscribes to an incoming chat message
42
+ def on_message(opts = {}, &block)
43
+ @messaging.subscribe :chat_message do |msg|
44
+ puts "(#{msg.payload[:iss]}, #{msg.payload[:jti]})"
45
+ cm = SelfSDK::Chat::Message.new(self, msg.payload[:aud], msg.payload, @messaging.client.jwt.auth_token, @client.self_url)
46
+
47
+ cm.mark_as_delivered unless opts[:mark_as_delivered] == false
48
+ cm.mark_as_read if opts[:mark_as_read] == true
49
+
50
+ block.call(cm)
51
+ end
52
+ end
53
+
54
+ # Sends a message to confirm a list of messages (identified by it's cids)
55
+ # have been delivered.
56
+ #
57
+ # @param recipients [array] list of recipients to send the message to.
58
+ # @param cids [array] list of message cids to be marked as delivered.
59
+ # @param gid [string] group id where the conversation ids are referenced.
60
+ def delivered(recipients, cids, gid = nil)
61
+ confirm('delivered', recipients, cids, gid)
62
+ end
63
+
64
+ # Sends a message to confirm a list of messages (identified by it's cids)
65
+ # have been read.
66
+ #
67
+ # @param recipients [array] list of recipients to send the message to.
68
+ # @param cids [array] list of message cids to be marked as read.
69
+ # @param gid [string] group id where the conversation ids are referenced.
70
+ def read(recipients, cids, gid = nil)
71
+ confirm('read', recipients, cids, gid)
72
+ end
73
+
74
+ # Modifies a previously sent message
75
+ #
76
+ # @param recipients [array] list of recipients to send the message to.
77
+ # @param cids [array] list of message cids to be marked as read.
78
+ # @param body [string] the new body to replace the previous one.
79
+ # @param gid [string] group id where the conversation ids are referenced.
80
+ def edit(recipients, cid, body, gid = nil)
81
+ send(recipients, typ: "chat.message.edit",
82
+ cid: cid,
83
+ msg: body,
84
+ gid: gid)
85
+ end
86
+
87
+ # Sends a message to delete a specific message.
88
+ #
89
+ # @param recipient [string] the recipient of the message
90
+ # @param cid [string] message cid to be marked as read.
91
+ # @param gid [string] group id where the conversation ids are referenced.
92
+ def delete(recipients, cids, gid = nil)
93
+ cids = [cids] if cids.is_a? String
94
+ send(recipients, typ: "chat.message.delete",
95
+ cids: cids,
96
+ gid: gid)
97
+ end
98
+
99
+ def on_invite(&block)
100
+ @messaging.subscribe :chat_invite do |msg|
101
+ g = SelfSDK::Chat::Group.new(self, msg.payload)
102
+ block.call(g)
103
+ end
104
+ end
105
+
106
+ def on_join(&block)
107
+ @messaging.subscribe :chat_join do |msg|
108
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
109
+ end
110
+ end
111
+
112
+ def on_leave(&block)
113
+ @messaging.subscribe :chat_remove do |msg|
114
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
115
+ end
116
+ end
117
+
118
+ # Invite sends group invitation to a list of members.
119
+ #
120
+ # @param gid [string] group id.
121
+ # @param name [string] name of the group.
122
+ # @param members [array] list of group members.
123
+ def invite(gid, name, members, opts = {})
124
+ payload = { typ: "chat.invite",
125
+ gid: gid,
126
+ name: name,
127
+ members: members }
128
+
129
+ if opts.key? :data
130
+ obj = SelfSDK::Chat::FileObject.new(@messaging.client.jwt.auth_token, @client.self_url)
131
+ obj_payload = obj.build_from_data("", opts[:data], opts[:mime]).to_payload
132
+ obj_payload.delete(:name)
133
+ payload.merge! obj_payload
134
+ end
135
+
136
+ @messaging.send(members, payload)
137
+ SelfSDK::Chat::Group.new(self, payload)
138
+ end
139
+
140
+ # Join a group
141
+ #
142
+ # @param gid [string] group id.
143
+ # @param members [array] list of group members.
144
+ def join(gid, members)
145
+ send(members, typ: 'chat.join', gid: gid, aud: gid)
146
+ end
147
+
148
+ # Leaves a group
149
+ #
150
+ # @param gid [string] group id.
151
+ # @members members [array] list of group members.
152
+ def leave(gid, members)
153
+ send(members, typ: "chat.remove", gid: gid )
154
+ end
155
+
156
+ private
157
+
158
+ # sends a confirmation for a list of messages to a list of recipients.
159
+ def confirm(action, recipients, cids, gid = nil)
160
+ cids = [cids] if cids.is_a? String
161
+ gid = recipients if gid.nil? || gid.empty?
162
+ send(recipients, typ: "chat.message.#{action}",
163
+ cids: cids,
164
+ gid: gid)
165
+ end
166
+
167
+ # sends a message to a list of recipients.
168
+ def send(recipients, body)
169
+ recipients = [recipients] if recipients.is_a? String
170
+ m = []
171
+ recipients.each do |r|
172
+ m << @messaging.send(r, body)
173
+ end
174
+ m
175
+ end
176
+ end
177
+ end
178
+ end
@@ -77,21 +77,20 @@ module SelfSDK
77
77
  @client.uuid_observer[cid]
78
78
  end
79
79
 
80
-
81
80
  # Send custom mmessage
82
81
  #
83
- # @param recipient [string] recipient for the message
82
+ # @param recipients [String|array] recipient for the message
84
83
  # @param type [string] message type
85
84
  # @param request [hash] message to be sent
86
- def send(recipient, request)
87
- request[:jti] = SecureRandom.uuid
85
+ def send(recipients, request)
86
+ request[:jti] = SecureRandom.uuid unless request.include?(:jti)
88
87
  request[:iss] = @client.jwt.id
89
- request[:sub] = recipient
90
- request[:iat] = SelfSDK::Time.now.strftime('%FT%TZ'),
91
- request[:exp] = (SelfSDK::Time.now + 300).strftime('%FT%TZ'),
92
- request[:cid] = SecureRandom.uuid unless request.include? :cid
88
+ request[:iat] = SelfSDK::Time.now.strftime('%FT%TZ')
89
+ request[:exp] = (SelfSDK::Time.now + 300).strftime('%FT%TZ')
90
+ request[:cid] = SecureRandom.uuid unless request.include?(:cid)
93
91
 
94
- @client.send_custom(recipient, request)
92
+ @client.send_custom(recipients, request)
93
+ request
95
94
  end
96
95
 
97
96
  def notify(recipient, message)
data/lib/sources.rb CHANGED
@@ -33,8 +33,15 @@ module SelfSDK
33
33
  types = { authentication_request: SelfSDK::Messages::AuthenticationReq::MSG_TYPE,
34
34
  authentication_response: SelfSDK::Messages::AuthenticationResp::MSG_TYPE,
35
35
  fact_request: SelfSDK::Messages::FactRequest::MSG_TYPE,
36
- fact_response: SelfSDK::Messages::FactResponse::MSG_TYPE }
37
- raise "invalid message type" unless types.key? s
36
+ fact_response: SelfSDK::Messages::FactResponse::MSG_TYPE,
37
+ chat_message: SelfSDK::Messages::ChatMessage::MSG_TYPE,
38
+ chat_message_deivered: SelfSDK::Messages::ChatMessageDelivered::MSG_TYPE,
39
+ chat_message_read: SelfSDK::Messages::ChatMessageRead::MSG_TYPE,
40
+ chat_invite: SelfSDK::Messages::ChatInvite::MSG_TYPE,
41
+ chat_join: SelfSDK::Messages::ChatJoin::MSG_TYPE,
42
+ chat_remove: SelfSDK::Messages::ChatRemove::MSG_TYPE,
43
+ }
44
+ raise "invalid message type '#{s}'" unless types.key? s
38
45
  return types[s]
39
46
  end
40
47
 
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.179
4
+ version: 0.0.180
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aldgate Ventures
@@ -226,14 +226,14 @@ dependencies:
226
226
  requirements:
227
227
  - - "~>"
228
228
  - !ruby/object:Gem::Version
229
- version: '12.3'
229
+ version: '13.0'
230
230
  type: :development
231
231
  prerelease: false
232
232
  version_requirements: !ruby/object:Gem::Requirement
233
233
  requirements:
234
234
  - - "~>"
235
235
  - !ruby/object:Gem::Version
236
- version: '12.3'
236
+ version: '13.0'
237
237
  - !ruby/object:Gem::Dependency
238
238
  name: rubocop
239
239
  requirement: !ruby/object:Gem::Requirement
@@ -326,6 +326,9 @@ extra_rdoc_files: []
326
326
  files:
327
327
  - lib/acl.rb
328
328
  - lib/authenticated.rb
329
+ - lib/chat/file_object.rb
330
+ - lib/chat/group.rb
331
+ - lib/chat/message.rb
329
332
  - lib/client.rb
330
333
  - lib/crypto.rb
331
334
  - lib/jwt_service.rb
@@ -335,6 +338,12 @@ files:
335
338
  - lib/messages/authentication_req.rb
336
339
  - lib/messages/authentication_resp.rb
337
340
  - lib/messages/base.rb
341
+ - lib/messages/chat_invite.rb
342
+ - lib/messages/chat_join.rb
343
+ - lib/messages/chat_message.rb
344
+ - lib/messages/chat_message_delivered.rb
345
+ - lib/messages/chat_message_read.rb
346
+ - lib/messages/chat_remove.rb
338
347
  - lib/messages/fact.rb
339
348
  - lib/messages/fact_request.rb
340
349
  - lib/messages/fact_response.rb
@@ -343,6 +352,7 @@ files:
343
352
  - lib/ntptime.rb
344
353
  - lib/selfsdk.rb
345
354
  - lib/services/auth.rb
355
+ - lib/services/chat.rb
346
356
  - lib/services/facts.rb
347
357
  - lib/services/identity.rb
348
358
  - lib/services/messaging.rb