selfsdk 0.0.179 → 0.0.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|