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 +4 -4
- data/lib/chat/file_object.rb +97 -0
- data/lib/chat/group.rb +51 -0
- data/lib/chat/message.rb +101 -0
- data/lib/crypto.rb +78 -40
- data/lib/messages/authentication_req.rb +2 -2
- data/lib/messages/base.rb +3 -2
- data/lib/messages/chat_invite.rb +19 -0
- data/lib/messages/chat_join.rb +19 -0
- data/lib/messages/chat_message.rb +23 -0
- data/lib/messages/chat_message_delivered.rb +21 -0
- data/lib/messages/chat_message_read.rb +20 -0
- data/lib/messages/chat_remove.rb +19 -0
- data/lib/messages/fact_request.rb +3 -3
- data/lib/messages/fact_response.rb +1 -1
- data/lib/messages/message.rb +25 -1
- data/lib/messaging.rb +32 -18
- data/lib/selfsdk.rb +6 -0
- data/lib/services/chat.rb +178 -0
- data/lib/services/messaging.rb +8 -9
- data/lib/sources.rb +9 -2
- metadata +13 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad25868d7199141b1efdfaaa9a6dd3e54584ba9bcd3422859f6e1ea0bb04524c
|
4
|
+
data.tar.gz: e656e160c2256e13067554873bc77995fd1939b954d7883dee59c6c139bbcb90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/chat/message.rb
ADDED
@@ -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,
|
36
|
-
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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,
|
48
|
-
@messaging.encryption_client.encrypt(message,
|
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 =
|
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
|
data/lib/messages/message.rb
CHANGED
@@ -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(
|
100
|
-
|
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 =
|
104
|
-
m.recipient = "#{
|
105
|
-
m.ciphertext =
|
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
|
data/lib/services/messaging.rb
CHANGED
@@ -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
|
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(
|
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[:
|
90
|
-
request[:
|
91
|
-
request[:
|
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(
|
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
|
-
|
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.
|
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: '
|
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: '
|
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
|