selfsdk 0.0.179 → 0.0.180

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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