selfsdk 0.0.179 → 0.0.184

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: 2c16f1734a708b0ebd37ca4fa0cea0e48db09344d4efb0654e5b4df6a37fd2d4
4
+ data.tar.gz: 32313fa1c28a57370c43d7100bdacc06bad28f0722ba0589a63c24bd0327bfd2
5
5
  SHA512:
6
- metadata.gz: ef6bb03cab8de8e44af7fad1f19a623345c6cd6fe00a4c1e0f35ab526e6b6523e2d8cd668c66d17cd85b5cf254f31a9e0dad4bbfc9c0a4301c3d67a044910d2f
7
- data.tar.gz: e7d3ce5591ea749537a6a2ee580b240b7033a6b4f6f2ba8fa3d64d11814d722844862e40a06c495a447bda01888be2f9b4e085fedb5534a5a89a75c7cfa4e965
6
+ metadata.gz: 9a015baa1ed364efcc422af5de6a54f87cbb953cdb45de7f397aa8cd2273b99c8ce4f6fbba0fc62f0ef803f38edf8155fbb1e9e14042b4f4ff76b391147da614
7
+ data.tar.gz: dfec1973db212e016774292d57bda452727cadd201ecdb80ab0f507a97ff3df244887ffde06704a618de63e0d101c75b7e8c879ff983654892d9496a50db7889
@@ -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 => e
51
+ ::SelfSDK.logger.warn(" there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
52
+ ::SelfSDK.logger.warn(e)
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,28 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_msgproto'
6
+ require_relative 'base'
7
+ require_relative '../ntptime'
8
+
9
+ module SelfSDK
10
+ module Messages
11
+ class Chat < Base
12
+ def parse(input, envelope=nil)
13
+ @input = input
14
+ @payload = get_payload input
15
+ @id = @payload[:jti]
16
+ @from = @payload[:iss]
17
+ @to = @payload[:sub]
18
+ @audience = payload[:aud]
19
+ @expires = @payload[:exp]
20
+
21
+ if envelope
22
+ issuer = envelope.sender.split(":")
23
+ @from_device = issuer.last
24
+ end
25
+ end
26
+ end
27
+ end
28
+ 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 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
@@ -34,6 +34,7 @@ module SelfSDK
34
34
  @options = opts.fetch(:options, false)
35
35
  @description = opts.include?(:description) ? opts[:description] : nil
36
36
  @exp_timeout = opts.fetch(:exp_timeout, DEFAULT_EXP_TIMEOUT)
37
+ @allowed_for = opts.fetch(:allowed_for, nil)
37
38
 
38
39
  @intermediary = if opts.include?(:intermediary)
39
40
  opts[:intermediary]
@@ -89,6 +90,7 @@ module SelfSDK
89
90
  }
90
91
  b[:options] = @options unless (@options.nil? || @options == false)
91
92
  b[:description] = @description unless (@description.nil? || @description.empty?)
93
+ b[:allowed_until] = (SelfSDK::Time.now + @allowed_for).strftime('%FT%TZ') unless @allowed_for.nil?
92
94
  b
93
95
  end
94
96
 
@@ -98,14 +100,14 @@ module SelfSDK
98
100
  @to_device = to_device
99
101
  if @intermediary.nil?
100
102
  recipient = "#{@to}:#{@to_device}"
101
- ciphertext = encrypt_message(@jwt.prepare(body), @to, @to_device)
103
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @to, device_id: @to_device}])
102
104
  else
103
105
  recipient = "#{@intermediary}:#{@to_device}"
104
- ciphertext = encrypt_message(@jwt.prepare(body), @intermediary, @to_device)
106
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @intermediary, device_id: @to_device}])
105
107
  end
106
108
 
107
109
  m = SelfMsg::Message.new
108
- m.id = SecureRandom.uuid
110
+ m.id = @id
109
111
  m.sender = "#{@jwt.id}:#{@messaging.device_id}"
110
112
  m.recipient = recipient
111
113
  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,38 +86,56 @@ 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
92
93
  end
93
94
 
95
+ def session?(identifier, device)
96
+ path = @encryption_client.session_path(identifier, device)
97
+ File.file?(path)
98
+ end
99
+
94
100
  # Send custom mmessage
95
101
  #
96
102
  # @param recipient [string] selfID to be requested
97
103
  # @param type [string] message type
98
104
  # @param request [hash] original message requesing information
99
- def send_custom(recipient, request_body)
100
- @client.devices(recipient).each do |to_device|
105
+ def send_custom(recipients, request_body)
106
+ # convert argument into an array if is a string
107
+ recipients = [recipients] if recipients.is_a? String
108
+
109
+ # send to current identity devices except the current one.
110
+ recipients |= [@jwt.id]
111
+
112
+ # build recipients list
113
+ recs = []
114
+ recipients.each do |r|
115
+ @client.devices(r).each do |to_device|
116
+ recs << { id: r, device_id: to_device }
117
+ end
118
+ end
119
+
120
+ SelfSDK.logger.info "sending custom message #{request_body.to_json}"
121
+ current_device = "#{@jwt.id}:#{@device_id}"
122
+
123
+ recs.each do |r|
124
+ next if current_device == "#{r[:id]}:#{r[:device_id]}"
125
+
126
+ request_body[:sub] = r[:id]
127
+ request_body[:aud] = r[:id] unless request_body.key?(:aud)
128
+ ciphertext = @encryption_client.encrypt(@jwt.prepare(request_body), recs)
129
+
101
130
  m = SelfMsg::Message.new
102
131
  m.id = SecureRandom.uuid
103
- m.sender = "#{@jwt.id}:#{@device_id}"
104
- m.recipient = "#{recipient}:#{to_device}"
105
- m.ciphertext = @jwt.prepare(request_body)
132
+ m.sender = current_device
133
+ m.recipient = "#{r[:id]}:#{r[:device_id]}"
134
+ m.ciphertext = ciphertext
106
135
 
136
+ SelfSDK.logger.info " -> to #{m.recipient}"
107
137
  send_message m
108
138
  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
139
  end
122
140
 
123
141
  # Allows incomming messages from the given identity
@@ -159,6 +177,7 @@ module SelfSDK
159
177
  #
160
178
  # @params msg [SelfMsg::Message] message object to be sent
161
179
  def send_and_wait_for_response(msgs, original)
180
+ SelfSDK.logger.info "sending/wait for #{msgs.first.id}"
162
181
  wait_for msgs.first.id, original do
163
182
  msgs.each do |msg|
164
183
  send_message msg
@@ -414,7 +433,7 @@ module SelfSDK
414
433
  rescue StandardError => e
415
434
  p "Error processing incoming message #{input.to_json}"
416
435
  SelfSDK.logger.info e
417
- p e.backtrace
436
+ # p e.backtrace
418
437
  nil
419
438
  end
420
439
 
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, identity)
89
+ end
90
+
85
91
  def app_id
86
92
  client.jwt.id
87
93
  end
@@ -0,0 +1,210 @@
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 = @messaging.client.client.jwt.id
18
+ @auth_token = @messaging.client.client.jwt.auth_token
19
+ @self_url = @messaging.client.client.self_url
20
+ end
21
+
22
+ # Sends a message to a list of recipients.
23
+ #
24
+ # @param recipients [array] list of recipients to send the message to.
25
+ # @param body [string] the message content to be sent
26
+ def message(recipients, body, opts = {})
27
+ payload = {
28
+ typ: "chat.message",
29
+ msg: body,
30
+ }
31
+ payload[:jti] = opts[:jti] if opts.key? :jti
32
+ payload[:aud] = opts[:gid] if opts.key? :gid
33
+ payload[:gid] = opts[:gid] if opts.key? :gid
34
+ payload[:rid] = opts[:rid] if opts.key? :rid
35
+ payload[:objects] = opts[:objects] if opts.key? :objects
36
+
37
+ m = SelfSDK::Chat::Message.new(self, recipients, payload, @auth_token, @self_url)
38
+ _req = send(m.recipients, m.payload)
39
+
40
+ m
41
+ end
42
+
43
+ # Subscribes to an incoming chat message
44
+ def on_message(opts = {}, &block)
45
+ @messaging.subscribe :chat_message do |msg|
46
+ cm = SelfSDK::Chat::Message.new(self, msg.payload[:aud], msg.payload, @auth_token, @self_url)
47
+
48
+ cm.mark_as_delivered unless opts[:mark_as_delivered] == false
49
+ cm.mark_as_read if opts[:mark_as_read] == true
50
+
51
+ block.call(cm)
52
+ end
53
+ end
54
+
55
+ # Sends a message to confirm a list of messages (identified by it's cids)
56
+ # have been delivered.
57
+ #
58
+ # @param recipients [array] list of recipients to send the message to.
59
+ # @param cids [array] list of message cids to be marked as delivered.
60
+ # @param gid [string] group id where the conversation ids are referenced.
61
+ def delivered(recipients, cids, gid = nil)
62
+ confirm('delivered', recipients, cids, gid)
63
+ end
64
+
65
+ # Sends a message to confirm a list of messages (identified by it's cids)
66
+ # have been read.
67
+ #
68
+ # @param recipients [array] list of recipients to send the message to.
69
+ # @param cids [array] list of message cids to be marked as read.
70
+ # @param gid [string] group id where the conversation ids are referenced.
71
+ def read(recipients, cids, gid = nil)
72
+ confirm('read', recipients, cids, gid)
73
+ end
74
+
75
+ # Modifies a previously sent message
76
+ #
77
+ # @param recipients [array] list of recipients to send the message to.
78
+ # @param cids [array] list of message cids to be marked as read.
79
+ # @param body [string] the new body to replace the previous one.
80
+ # @param gid [string] group id where the conversation ids are referenced.
81
+ def edit(recipients, cid, body, gid = nil)
82
+ send(recipients, typ: "chat.message.edit",
83
+ cid: cid,
84
+ msg: body,
85
+ gid: gid)
86
+ end
87
+
88
+ # Sends a message to delete a specific message.
89
+ #
90
+ # @param recipient [string] the recipient of the message
91
+ # @param cid [string] message cid to be marked as read.
92
+ # @param gid [string] group id where the conversation ids are referenced.
93
+ def delete(recipients, cids, gid = nil)
94
+ cids = [cids] if cids.is_a? String
95
+ send(recipients, typ: "chat.message.delete",
96
+ cids: cids,
97
+ gid: gid)
98
+ end
99
+
100
+ def on_invite(&block)
101
+ @messaging.subscribe :chat_invite do |msg|
102
+ g = SelfSDK::Chat::Group.new(self, msg.payload)
103
+ block.call(g)
104
+ end
105
+ end
106
+
107
+ def on_join(&block)
108
+ @messaging.subscribe :chat_join do |msg|
109
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
110
+ end
111
+ end
112
+
113
+ def on_leave(&block)
114
+ @messaging.subscribe :chat_remove do |msg|
115
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
116
+ end
117
+ end
118
+
119
+ # Invite sends group invitation to a list of members.
120
+ #
121
+ # @param gid [string] group id.
122
+ # @param name [string] name of the group.
123
+ # @param members [array] list of group members.
124
+ def invite(gid, name, members, opts = {})
125
+ payload = { typ: "chat.invite",
126
+ gid: gid,
127
+ name: name,
128
+ members: members }
129
+
130
+ if opts.key? :data
131
+ obj = SelfSDK::Chat::FileObject.new(@auth_token, @self_url)
132
+ obj_payload = obj.build_from_data("", opts[:data], opts[:mime]).to_payload
133
+ obj_payload.delete(:name)
134
+ payload.merge! obj_payload
135
+ end
136
+
137
+ @messaging.send(members, payload)
138
+ SelfSDK::Chat::Group.new(self, payload)
139
+ end
140
+
141
+ # Join a group
142
+ #
143
+ # @param gid [string] group id.
144
+ # @param members [array] list of group members.
145
+ def join(gid, members)
146
+ # Allow incoming connections from the given members
147
+
148
+ # Create missing sessions with group members.
149
+ create_missing_sessions(members)
150
+
151
+ # Send joining confirmation.
152
+ send(members, typ: 'chat.join', gid: gid, aud: gid)
153
+ end
154
+
155
+ # Leaves a group
156
+ #
157
+ # @param gid [string] group id.
158
+ # @members members [array] list of group members.
159
+ def leave(gid, members)
160
+ send(members, typ: "chat.remove", gid: gid )
161
+ end
162
+
163
+ private
164
+
165
+ # sends a confirmation for a list of messages to a list of recipients.
166
+ def confirm(action, recipients, cids, gid = nil)
167
+ cids = [cids] if cids.is_a? String
168
+ gid = recipients if gid.nil? || gid.empty?
169
+ send(recipients, typ: "chat.message.#{action}",
170
+ cids: cids,
171
+ gid: gid)
172
+ end
173
+
174
+ # sends a message to a list of recipients.
175
+ def send(recipients, body)
176
+ recipients = [recipients] if recipients.is_a? String
177
+ m = []
178
+ recipients.each do |r|
179
+ m << @messaging.send(r, body)
180
+ end
181
+ m
182
+ end
183
+
184
+ # Group invites may come with members of the group we haven't set up a session
185
+ # previously, for those identitiese need to establish a session, but only if
186
+ # our identity appears before the others in the list members.
187
+ def create_missing_sessions(members)
188
+ return if members.empty?
189
+
190
+ posterior_members = false
191
+ requests = []
192
+
193
+ members.each do |m|
194
+ if posterior_members
195
+ @client.devices(m).each do |d|
196
+ continue unless @messaging.client.session?(m, d)
197
+
198
+ requests << @messaging.send("#{m}:#{d}", {
199
+ typ: 'sessions.create',
200
+ aud: m
201
+ })
202
+ end
203
+ end
204
+
205
+ posterior_members = true if m == @app_id
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -40,6 +40,7 @@ module SelfSDK
40
40
  # @param [Hash] opts the options to authenticate.
41
41
  # @option opts [String] :cid The unique identifier of the authentication request.
42
42
  # @option opts [Integer] :exp_timeout timeout in seconds to expire the request.
43
+ # @option opts [Integer] :allowed_for number of seconds for enabling recurrent requests.
43
44
  # @return [Object] SelfSDK:::Messages::FactRequest
44
45
  def request(selfid, facts, opts = {}, &block)
45
46
  SelfSDK.logger.info "authenticating #{selfid}"
@@ -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.184
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,13 @@ 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.rb
342
+ - lib/messages/chat_invite.rb
343
+ - lib/messages/chat_join.rb
344
+ - lib/messages/chat_message.rb
345
+ - lib/messages/chat_message_delivered.rb
346
+ - lib/messages/chat_message_read.rb
347
+ - lib/messages/chat_remove.rb
338
348
  - lib/messages/fact.rb
339
349
  - lib/messages/fact_request.rb
340
350
  - lib/messages/fact_response.rb
@@ -343,6 +353,7 @@ files:
343
353
  - lib/ntptime.rb
344
354
  - lib/selfsdk.rb
345
355
  - lib/services/auth.rb
356
+ - lib/services/chat.rb
346
357
  - lib/services/facts.rb
347
358
  - lib/services/identity.rb
348
359
  - lib/services/messaging.rb