add participant to group chat
This commit is contained in:
parent
d5d781410f
commit
89c85025db
|
@ -10,20 +10,22 @@
|
||||||
[syng-im.protocol.state.delivery :refer [add-pending-message]]
|
[syng-im.protocol.state.delivery :refer [add-pending-message]]
|
||||||
[syng-im.protocol.state.group-chat :refer [save-keypair
|
[syng-im.protocol.state.group-chat :refer [save-keypair
|
||||||
get-keypair
|
get-keypair
|
||||||
|
get-peer-identities
|
||||||
get-identities
|
get-identities
|
||||||
save-identities]]
|
save-identities]]
|
||||||
[syng-im.protocol.delivery :refer [start-delivery-loop]]
|
[syng-im.protocol.delivery :refer [start-delivery-loop]]
|
||||||
[syng-im.protocol.web3 :refer [listen make-msg
|
[syng-im.protocol.web3 :refer [listen
|
||||||
|
make-msg
|
||||||
post-msg
|
post-msg
|
||||||
make-web3
|
make-web3
|
||||||
new-identity]]
|
new-identity]]
|
||||||
[syng-im.protocol.handler :refer [handle-incoming-whisper-msg]]
|
[syng-im.protocol.handler :refer [handle-incoming-whisper-msg]]
|
||||||
[syng-im.protocol.user-handler :refer [invoke-user-handler]]
|
[syng-im.protocol.user-handler :refer [invoke-user-handler]]
|
||||||
[syng-im.utils.encryption :refer [new-keypair]])
|
[syng-im.utils.encryption :refer [new-keypair]]
|
||||||
|
[syng-im.protocol.group-chat :refer [send-group-msg]]
|
||||||
|
[syng-im.protocol.defaults :refer [default-content-type]])
|
||||||
(:require-macros [cljs.core.async.macros :refer [go]]))
|
(:require-macros [cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
(def default-content-type "text/plain")
|
|
||||||
|
|
||||||
(defn create-connection [ethereum-rpc-url]
|
(defn create-connection [ethereum-rpc-url]
|
||||||
(make-web3 ethereum-rpc-url))
|
(make-web3 ethereum-rpc-url))
|
||||||
|
|
||||||
|
@ -49,9 +51,10 @@
|
||||||
:delivery-failed [msg-id]
|
:delivery-failed [msg-id]
|
||||||
:new-group-chat [from group-id]
|
:new-group-chat [from group-id]
|
||||||
:group-chat-invite-acked [from group-id]
|
:group-chat-invite-acked [from group-id]
|
||||||
|
:group-new-participant [identity group-id]
|
||||||
:initialized [identity]
|
:initialized [identity]
|
||||||
|
|
||||||
:new-msg, msg-acked should be handled idempotently (may be called multiple times for the same msg-id)
|
:new-msg, new-group-msg, msg-acked should be handled idempotently (may be called multiple times for the same msg-id)
|
||||||
"
|
"
|
||||||
[{:keys [handler ethereum-rpc-url storage identity active-group-ids]}]
|
[{:keys [handler ethereum-rpc-url storage identity active-group-ids]}]
|
||||||
(set-storage storage)
|
(set-storage storage)
|
||||||
|
@ -78,31 +81,25 @@
|
||||||
(post-msg (connection) msg)
|
(post-msg (connection) msg)
|
||||||
new-msg))
|
new-msg))
|
||||||
|
|
||||||
(defn send-group-msg [{:keys [group-id content]}]
|
(defn send-group-user-msg [{:keys [group-id content]}]
|
||||||
(let [store (storage)
|
(send-group-msg {:group-id group-id
|
||||||
{public-key :public} (get-keypair store group-id)
|
:type :group-user-msg
|
||||||
{:keys [msg-id msg] :as new-msg} (make-msg {:from (state/my-identity)
|
:payload {:content content
|
||||||
:topics [group-id]
|
:content-type default-content-type}}))
|
||||||
:encrypt? true
|
|
||||||
:public-key public-key
|
|
||||||
:payload {:content content
|
|
||||||
:content-type default-content-type}
|
|
||||||
:clear-info {:group-id group-id
|
|
||||||
:type :group-user-msg}})]
|
|
||||||
(add-pending-message msg-id msg {:identities (get-identities store group-id)})
|
|
||||||
(post-msg (connection) msg)
|
|
||||||
new-msg))
|
|
||||||
|
|
||||||
(defn start-group-chat [identities]
|
(defn start-group-chat [identities]
|
||||||
(let [group-topic (random/id)
|
(let [group-topic (random/id)
|
||||||
keypair (new-keypair)
|
keypair (new-keypair)
|
||||||
store (storage)
|
store (storage)
|
||||||
connection (connection)]
|
connection (connection)
|
||||||
|
my-identity (state/my-identity)
|
||||||
|
identities (-> (set identities)
|
||||||
|
(conj my-identity))]
|
||||||
(save-keypair store group-topic keypair)
|
(save-keypair store group-topic keypair)
|
||||||
(save-identities store group-topic identities)
|
(save-identities store group-topic identities)
|
||||||
(listen connection handle-incoming-whisper-msg {:topics [group-topic]})
|
(listen connection handle-incoming-whisper-msg {:topics [group-topic]})
|
||||||
(doseq [ident identities]
|
(doseq [ident identities :when (not (= ident my-identity))]
|
||||||
(let [{:keys [msg-id msg]} (make-msg {:from (state/my-identity)
|
(let [{:keys [msg-id msg]} (make-msg {:from my-identity
|
||||||
:to ident
|
:to ident
|
||||||
:payload {:type :init-group-chat
|
:payload {:type :init-group-chat
|
||||||
:group-topic group-topic
|
:group-topic group-topic
|
||||||
|
@ -112,5 +109,25 @@
|
||||||
(post-msg connection msg)))
|
(post-msg connection msg)))
|
||||||
group-topic))
|
group-topic))
|
||||||
|
|
||||||
|
(defn group-add-participant [group-id new-peer-identity]
|
||||||
|
(let [store (storage)
|
||||||
|
connection (connection)
|
||||||
|
identities (-> (get-identities store group-id)
|
||||||
|
(conj new-peer-identity))
|
||||||
|
keypair (get-keypair store group-id)]
|
||||||
|
(save-identities store group-id identities)
|
||||||
|
(let [{:keys [msg-id msg]} (make-msg {:from (state/my-identity)
|
||||||
|
:to new-peer-identity
|
||||||
|
:payload {:type :init-group-chat
|
||||||
|
:group-topic group-id
|
||||||
|
:identities identities
|
||||||
|
:keypair keypair}})]
|
||||||
|
(add-pending-message msg-id msg {:internal? true})
|
||||||
|
(post-msg connection msg))
|
||||||
|
(send-group-msg {:group-id group-id
|
||||||
|
:type :group-new-participant
|
||||||
|
:payload {:identity new-peer-identity}
|
||||||
|
:internal? true})))
|
||||||
|
|
||||||
(defn current-connection []
|
(defn current-connection []
|
||||||
(connection))
|
(connection))
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
(ns syng-im.protocol.defaults
|
||||||
|
(:require [cljs-time.core :as t]))
|
||||||
|
|
||||||
|
(def default-content-type "text/plain")
|
||||||
|
|
||||||
|
(def max-retry-send-count 5)
|
||||||
|
(def ack-wait-timeout-ms (t/millis 5000))
|
||||||
|
(def check-delivery-interval-msg 100)
|
|
@ -5,13 +5,12 @@
|
||||||
[syng-im.protocol.state.delivery :as state]
|
[syng-im.protocol.state.delivery :as state]
|
||||||
[syng-im.protocol.state.state :as s]
|
[syng-im.protocol.state.state :as s]
|
||||||
[syng-im.protocol.web3 :as whisper]
|
[syng-im.protocol.web3 :as whisper]
|
||||||
[syng-im.protocol.user-handler :refer [invoke-user-handler]])
|
[syng-im.protocol.user-handler :refer [invoke-user-handler]]
|
||||||
|
[syng-im.protocol.defaults :refer [max-retry-send-count
|
||||||
|
ack-wait-timeout-ms
|
||||||
|
check-delivery-interval-msg]])
|
||||||
(:require-macros [cljs.core.async.macros :refer [go]]))
|
(:require-macros [cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
(def max-retry-send-count 5)
|
|
||||||
(def ack-wait-timeout-ms (t/millis 5000))
|
|
||||||
(def check-delivery-interval-msg 100)
|
|
||||||
|
|
||||||
(defn expired? [timestamp]
|
(defn expired? [timestamp]
|
||||||
(t/before? (t/plus timestamp ack-wait-timeout-ms) (t/now)))
|
(t/before? (t/plus timestamp ack-wait-timeout-ms) (t/now)))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
(ns syng-im.protocol.group-chat
|
||||||
|
(:require [syng-im.protocol.state.state :as state :refer [connection
|
||||||
|
storage]]
|
||||||
|
[syng-im.protocol.state.delivery :refer [add-pending-message]]
|
||||||
|
[syng-im.protocol.state.group-chat :refer [get-keypair
|
||||||
|
get-peer-identities]]
|
||||||
|
[syng-im.protocol.web3 :refer [make-msg
|
||||||
|
post-msg]]))
|
||||||
|
|
||||||
|
(defn send-group-msg [{:keys [group-id payload type internal?] :or {internal? false}}]
|
||||||
|
(let [store (storage)
|
||||||
|
{public-key :public} (get-keypair store group-id)
|
||||||
|
{:keys [msg-id msg] :as new-msg} (make-msg {:from (state/my-identity)
|
||||||
|
:topics [group-id]
|
||||||
|
:encrypt? true
|
||||||
|
:public-key public-key
|
||||||
|
:payload payload
|
||||||
|
:clear-info {:group-id group-id
|
||||||
|
:type type}})]
|
||||||
|
(add-pending-message msg-id msg {:identities (get-peer-identities store group-id)
|
||||||
|
:internal? internal?})
|
||||||
|
(post-msg (connection) msg)
|
||||||
|
new-msg))
|
|
@ -7,13 +7,16 @@
|
||||||
update-pending-message]]
|
update-pending-message]]
|
||||||
[syng-im.protocol.state.group-chat :refer [save-keypair
|
[syng-im.protocol.state.group-chat :refer [save-keypair
|
||||||
save-identities
|
save-identities
|
||||||
|
get-identities
|
||||||
chat-exists?
|
chat-exists?
|
||||||
get-keypair]]
|
get-keypair
|
||||||
|
add-identity]]
|
||||||
[syng-im.protocol.web3 :refer [to-ascii
|
[syng-im.protocol.web3 :refer [to-ascii
|
||||||
make-msg
|
make-msg
|
||||||
post-msg
|
post-msg
|
||||||
listen]]
|
listen]]
|
||||||
[syng-im.protocol.user-handler :refer [invoke-user-handler]]))
|
[syng-im.protocol.user-handler :refer [invoke-user-handler]]
|
||||||
|
[syng-im.protocol.defaults :refer [default-content-type]]))
|
||||||
|
|
||||||
(defn handle-ack [from {:keys [ack-msg-id] :as payload}]
|
(defn handle-ack [from {:keys [ack-msg-id] :as payload}]
|
||||||
(log/info "Got ack for message:" ack-msg-id "from:" from)
|
(log/info "Got ack for message:" ack-msg-id "from:" from)
|
||||||
|
@ -56,16 +59,28 @@
|
||||||
:identities identities
|
:identities identities
|
||||||
:group-id group-topic}))))
|
:group-id group-topic}))))
|
||||||
|
|
||||||
(defn handle-group-user-msg [web3 from {:keys [enc-payload group-id]}]
|
(defn decrypt-group-msg [group-id encrypted-payload]
|
||||||
(let [store (storage)
|
(let [store (storage)
|
||||||
{private-key :private} (get-keypair store group-id)
|
{private-key :private} (get-keypair store group-id)]
|
||||||
{:keys [msg-id] :as payload} (-> (decrypt private-key enc-payload)
|
(-> (decrypt private-key encrypted-payload)
|
||||||
(read-string)
|
(read-string)
|
||||||
(assoc :group-id group-id))]
|
(assoc :group-id group-id))))
|
||||||
|
|
||||||
|
(defn handle-group-user-msg [web3 from {:keys [enc-payload group-id]}]
|
||||||
|
(let [{:keys [msg-id] :as payload} (decrypt-group-msg group-id enc-payload)]
|
||||||
(send-ack web3 from msg-id)
|
(send-ack web3 from msg-id)
|
||||||
(invoke-user-handler :new-group-msg {:from from
|
(invoke-user-handler :new-group-msg {:from from
|
||||||
:payload payload})))
|
:payload payload})))
|
||||||
|
|
||||||
|
(defn handle-group-new-participant [web3 from {:keys [enc-payload group-id]}]
|
||||||
|
(let [store (storage)
|
||||||
|
{:keys [msg-id identity]} (decrypt-group-msg group-id enc-payload)]
|
||||||
|
(send-ack web3 from msg-id)
|
||||||
|
(add-identity store group-id identity)
|
||||||
|
(invoke-user-handler :group-new-participant {:identity identity
|
||||||
|
:group-id group-id
|
||||||
|
:from from})))
|
||||||
|
|
||||||
(defn handle-incoming-whisper-msg [web3 msg]
|
(defn handle-incoming-whisper-msg [web3 msg]
|
||||||
(log/info "Got whisper message:" msg)
|
(log/info "Got whisper message:" msg)
|
||||||
(let [{from :from
|
(let [{from :from
|
||||||
|
@ -81,5 +96,6 @@
|
||||||
:ack (handle-ack from payload)
|
:ack (handle-ack from payload)
|
||||||
:user-msg (handle-user-msg web3 from payload)
|
:user-msg (handle-user-msg web3 from payload)
|
||||||
:init-group-chat (handle-new-group-chat web3 from payload)
|
:init-group-chat (handle-new-group-chat web3 from payload)
|
||||||
:group-user-msg (handle-group-user-msg web3 from payload)))
|
:group-user-msg (handle-group-user-msg web3 from payload)
|
||||||
|
:group-new-participant (handle-group-new-participant web3 from payload)))
|
||||||
(log/warn "My identity:" (state/my-identity) "Message To:" to "Message is encrypted for someone else, ignoring"))))
|
(log/warn "My identity:" (state/my-identity) "Message To:" to "Message is encrypted for someone else, ignoring"))))
|
||||||
|
|
|
@ -22,8 +22,7 @@
|
||||||
(swap! state (fn [state]
|
(swap! state (fn [state]
|
||||||
(-> (assoc-in state [:pending-messages msg-id] {:msg msg
|
(-> (assoc-in state [:pending-messages msg-id] {:msg msg
|
||||||
:retry-count 0
|
:retry-count 0
|
||||||
:identities (when identities
|
:identities identities
|
||||||
(set identities))
|
|
||||||
:internal? internal?})
|
:internal? internal?})
|
||||||
(push-msg-to-delivery-queue msg-id)))))
|
(push-msg-to-delivery-queue msg-id)))))
|
||||||
([msg-id msg]
|
([msg-id msg]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
(ns syng-im.protocol.state.group-chat
|
(ns syng-im.protocol.state.group-chat
|
||||||
(:require [syng-im.protocol.state.storage :as s]))
|
(:require [syng-im.protocol.state.storage :as s]
|
||||||
|
[syng-im.protocol.state.state :as state]))
|
||||||
|
|
||||||
(defn topic-keypair-key [topic]
|
(defn topic-keypair-key [topic]
|
||||||
(str "group-chat.topic-keypair." topic))
|
(str "group-chat.topic-keypair." topic))
|
||||||
|
@ -15,10 +16,20 @@
|
||||||
(let [key (topic-identities-key topic)]
|
(let [key (topic-identities-key topic)]
|
||||||
(s/put storage key identities)))
|
(s/put storage key identities)))
|
||||||
|
|
||||||
|
(defn add-identity [storage topic identity]
|
||||||
|
(let [identities (get-identities storage topic)]
|
||||||
|
(when-not (contains? identities identity)
|
||||||
|
(->> (conj identities identity)
|
||||||
|
(save-identities storage group-id)))))
|
||||||
|
|
||||||
(defn get-identities [storage topic]
|
(defn get-identities [storage topic]
|
||||||
(let [key (topic-identities-key topic)]
|
(let [key (topic-identities-key topic)]
|
||||||
(s/get storage key)))
|
(s/get storage key)))
|
||||||
|
|
||||||
|
(defn get-peer-identities [storage topic]
|
||||||
|
(-> (get-identities storage topic)
|
||||||
|
(disj (state/my-identity))))
|
||||||
|
|
||||||
(defn chat-exists? [storage topic]
|
(defn chat-exists? [storage topic]
|
||||||
(let [key (topic-keypair-key topic)]
|
(let [key (topic-keypair-key topic)]
|
||||||
(s/contains-key? storage key)))
|
(s/contains-key? storage key)))
|
||||||
|
|
|
@ -69,6 +69,11 @@
|
||||||
<div>
|
<div>
|
||||||
<button id="start-group-chat-button">Start Group Chat</button>
|
<button id="start-group-chat-button">Start Group Chat</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="new-peer">Add Participant</label>
|
||||||
|
<input id="new-peer" type="text"/>
|
||||||
|
<button id="add-peer-button">Add Participant</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/compiled/syng_im.js" type="text/javascript"></script>
|
<script src="js/compiled/syng_im.js" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -34,13 +34,16 @@
|
||||||
:group-identities nil
|
:group-identities nil
|
||||||
:storage (map->MapStore {:m (atom {})})}))
|
:storage (map->MapStore {:m (atom {})})}))
|
||||||
|
|
||||||
|
(defn shorten [s]
|
||||||
|
(subs s 0 6))
|
||||||
|
|
||||||
(defn set-group-id! [group-id]
|
(defn set-group-id! [group-id]
|
||||||
(swap! state assoc :group-id group-id))
|
(swap! state assoc :group-id group-id))
|
||||||
|
|
||||||
(defn add-to-chat [element-id from content]
|
(defn add-to-chat [element-id from content]
|
||||||
(let [chat-area (g/getElement element-id)
|
(let [chat-area (g/getElement element-id)
|
||||||
chat (f/getValue chat-area)
|
chat (f/getValue chat-area)
|
||||||
chat (str chat (subs from 0 6) ": " content "\n")]
|
chat (str chat (shorten from) ": " content "\n")]
|
||||||
(f/setValue chat-area chat)))
|
(f/setValue chat-area chat)))
|
||||||
|
|
||||||
(defn set-group-identities [identities]
|
(defn set-group-identities [identities]
|
||||||
|
@ -48,6 +51,11 @@
|
||||||
(-> (g/getElement "to-identities")
|
(-> (g/getElement "to-identities")
|
||||||
(f/setValue ids))))
|
(f/setValue ids))))
|
||||||
|
|
||||||
|
(defn get-group-identities []
|
||||||
|
(-> (g/getElement "to-identities")
|
||||||
|
(f/getValue)
|
||||||
|
(s/split "\n")))
|
||||||
|
|
||||||
(defn start []
|
(defn start []
|
||||||
(let [rpc-url (-> (g/getElement "rpc-url")
|
(let [rpc-url (-> (g/getElement "rpc-url")
|
||||||
(f/getValue))]
|
(f/getValue))]
|
||||||
|
@ -77,6 +85,12 @@
|
||||||
:new-group-msg (let [{from :from
|
:new-group-msg (let [{from :from
|
||||||
{content :content} :payload} event]
|
{content :content} :payload} event]
|
||||||
(add-to-chat "group-chat" from content))
|
(add-to-chat "group-chat" from content))
|
||||||
|
:group-new-participant (let [{:keys [group-id identity from]} event]
|
||||||
|
(add-to-chat "group-chat" ":" (str (shorten from) " added " (shorten identity) " to group chat"))
|
||||||
|
(-> (get-group-identities)
|
||||||
|
(set)
|
||||||
|
(conj identity)
|
||||||
|
(set-group-identities)))
|
||||||
(add-to-chat "chat" ":" (str "Don't know how to handle " event-type))))})
|
(add-to-chat "chat" ":" (str "Don't know how to handle " event-type))))})
|
||||||
(e/listen (-> (g/getElement "msg")
|
(e/listen (-> (g/getElement "msg")
|
||||||
(goog.events.KeyHandler.))
|
(goog.events.KeyHandler.))
|
||||||
|
@ -98,18 +112,23 @@
|
||||||
(let [msg (-> (g/getElement "group-msg")
|
(let [msg (-> (g/getElement "group-msg")
|
||||||
(f/getValue))
|
(f/getValue))
|
||||||
group-id (:group-id @state)]
|
group-id (:group-id @state)]
|
||||||
(p/send-group-msg {:group-id group-id
|
(p/send-group-user-msg {:group-id group-id
|
||||||
:content msg})
|
:content msg})
|
||||||
(add-to-chat "group-chat" (p/my-identity) msg)))))))
|
(add-to-chat "group-chat" (p/my-identity) msg)))))))
|
||||||
|
|
||||||
(defn start-group-chat []
|
(defn start-group-chat []
|
||||||
(let [identities (-> (g/getElement "to-identities")
|
(let [identities (get-group-identities)]
|
||||||
(f/getValue)
|
|
||||||
(s/split "\n"))]
|
|
||||||
(add-to-chat "group-chat" ":" (str "Starting group chat with " identities))
|
(add-to-chat "group-chat" ":" (str "Starting group chat with " identities))
|
||||||
(let [group-id (p/start-group-chat identities)]
|
(let [group-id (p/start-group-chat identities)]
|
||||||
(set-group-id! group-id))))
|
(set-group-id! group-id))))
|
||||||
|
|
||||||
|
(defn add-new-peer-to-group []
|
||||||
|
(let [input (g/getElement "new-peer")
|
||||||
|
new-identity (-> input (f/getValue))
|
||||||
|
group-id (:group-id @state)]
|
||||||
|
(f/setValue input "")
|
||||||
|
(p/group-add-participant group-id new-identity)))
|
||||||
|
|
||||||
(let [button (g/getElement "connect-button")]
|
(let [button (g/getElement "connect-button")]
|
||||||
(e/listen button EventType/CLICK
|
(e/listen button EventType/CLICK
|
||||||
(fn [e]
|
(fn [e]
|
||||||
|
@ -123,6 +142,11 @@
|
||||||
(g/setProperties (g/getElement "to-identities") #js {:disabled "disabled"})
|
(g/setProperties (g/getElement "to-identities") #js {:disabled "disabled"})
|
||||||
(start-group-chat))))
|
(start-group-chat))))
|
||||||
|
|
||||||
|
(let [button (g/getElement "add-peer-button")]
|
||||||
|
(e/listen button EventType/CLICK
|
||||||
|
(fn [e]
|
||||||
|
(add-new-peer-to-group))))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
|
|
||||||
(p/init-protocol {:ethereum-rpc-url "http://localhost:4546"
|
(p/init-protocol {:ethereum-rpc-url "http://localhost:4546"
|
||||||
|
|
Loading…
Reference in New Issue