basic group chat

This commit is contained in:
michaelr 2016-03-09 22:18:38 +02:00
parent 1fd85efc7d
commit 7d1c1fa5c1
5 changed files with 93 additions and 68 deletions

View File

@ -35,13 +35,15 @@
(defn init-protocol
"Required [handler ethereum-rpc-url storage]
Optional [whisper-identity] - if not passed a new identity is created automatically
Optional [whisper-identity - if not passed a new identity is created automatically
active-group-ids - list of active group ids]
(fn handler [{:keys [event-type...}])
:event-type can be:
:new-msg - [from payload]
:new-group-msg [from payload]
:error - [error-msg details]
:msg-acked [msg-id from]
:delivery-failed [msg-id]
@ -51,7 +53,7 @@
:new-msg, msg-acked should be handled idempotently (may be called multiple times for the same msg-id)
"
[{:keys [handler ethereum-rpc-url storage identity]}]
[{:keys [handler ethereum-rpc-url storage identity active-group-ids]}]
(set-storage storage)
(set-handler handler)
(go
@ -62,6 +64,8 @@
(set-identity identity)
(listen connection handle-incoming-whisper-msg)
(start-delivery-loop)
(doseq [group-id active-group-ids]
(listen connection handle-incoming-whisper-msg {:topics [group-id]}))
(invoke-user-handler :initialized {:identity identity}))))
(defn send-user-msg [{:keys [to content]}]
@ -79,21 +83,24 @@
{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
:encrypt? true
:public-key public-key
:payload {:content content
:content-type default-content-type
:type :user-msg}})]
: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]
(let [group-topic (random/id)
keypair (new-keypair)]
(let [store (storage)]
(save-keypair store group-topic keypair)
(save-identities store group-topic identities))
keypair (new-keypair)
store (storage)
connection (connection)]
(save-keypair store group-topic keypair)
(save-identities store group-topic identities)
(listen connection handle-incoming-whisper-msg {:topics [group-topic]})
(doseq [ident identities]
(let [{:keys [msg-id msg]} (make-msg {:from (state/my-identity)
:to ident
@ -102,28 +109,8 @@
:identities identities
:keypair keypair}})]
(add-pending-message msg-id msg {:internal? true})
(post-msg (connection) msg)))
(post-msg connection msg)))
group-topic))
(defn current-connection []
(connection))

View File

@ -1,9 +0,0 @@
(ns syng-im.protocol.group-chat
(:require [syng-im.utils.random :as random]
[syng-im.utils.encryption :refer [new-keypair]]
[syng-im.protocol.state.group-chat :refer [save-keypair]]
[syng-im.protocol.state.state :refer [connection my-identity storage]]
[syng-im.protocol.web3 :refer [make-msg]]
[syng-im.protocol.state.delivery :refer [add-pending-message]]))

View File

@ -1,16 +1,18 @@
(ns syng-im.protocol.handler
(:require [cljs.reader :refer [read-string]]
[syng-im.utils.logging :as log]
[syng-im.utils.encryption :refer [decrypt]]
[syng-im.protocol.state.state :as state :refer [storage]]
[syng-im.protocol.state.delivery :refer [internal?
update-pending-message]]
[syng-im.protocol.state.group-chat :refer [save-keypair
save-identities
chat-exists?]]
chat-exists?
get-keypair]]
[syng-im.protocol.web3 :refer [to-ascii
make-msg
post-msg
]]
listen]]
[syng-im.protocol.user-handler :refer [invoke-user-handler]]))
(defn handle-ack [from {:keys [ack-msg-id] :as payload}]
@ -20,7 +22,7 @@
(when-not internal-message?
(invoke-user-handler :msg-acked {:msg-id ack-msg-id
:from from}))
(when-let [group-topic (payload :group-topic)]
(when-let [group-topic (payload :group-invite)]
(invoke-user-handler :group-chat-invite-acked {:from from
:group-id group-topic}))))
@ -41,16 +43,29 @@
(invoke-user-handler :new-msg {:from from
:payload payload}))
(declare handle-incoming-whisper-msg)
(defn handle-new-group-chat [web3 from {:keys [group-topic keypair identities msg-id]}]
(send-ack web3 from msg-id {:group-topic group-topic})
(send-ack web3 from msg-id {:group-invite group-topic})
(let [store (storage)]
(when-not (chat-exists? store group-topic)
(listen web3 handle-incoming-whisper-msg {:topics [group-topic]})
(save-keypair store group-topic keypair)
(save-identities store group-topic identities)
(invoke-user-handler :new-group-chat {:from from
:identities identities
:group-id group-topic}))))
(defn handle-group-user-msg [web3 from {:keys [enc-payload group-id]}]
(let [store (storage)
{private-key :private} (get-keypair store group-id)
{:keys [msg-id] :as payload} (-> (decrypt private-key enc-payload)
(read-string)
(assoc :group-id group-id))]
(send-ack web3 from msg-id)
(invoke-user-handler :new-group-msg {:from from
:payload payload})))
(defn handle-incoming-whisper-msg [web3 msg]
(log/info "Got whisper message:" msg)
(let [{from :from
@ -58,13 +73,13 @@
topics :topics ;; always empty (bug in go-ethereum?)
payload :payload
:as msg} (js->clj msg :keywordize-keys true)]
(if (= to (state/my-identity))
(let [{msg-type :type
msg-id :msg-id
:as payload} (->> (to-ascii payload)
(read-string))]
(if (or (= to "0x0")
(= to (state/my-identity)))
(let [{msg-type :type :as payload} (->> (to-ascii payload)
(read-string))]
(case msg-type
:ack (handle-ack 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)))
(log/warn "My identity:" (state/my-identity) "Message To:" to "Message is encrypted for someone else, ignoring"))))

View File

@ -34,7 +34,7 @@
(do
(log/error (str error-msg ":") error)
(invoke-user-handler :error {:error-msg error-msg
:details error}))
:details error}))
(put! result-channel result))
(close! result-channel)))
@ -54,11 +54,16 @@
(let [error-msg "Call to shh.post() failed"]
(log/error (str error-msg ":") error)
(invoke-user-handler :error {:error-msg error-msg
:details error}))))))))
:details error}))))))))
(defn encrypt-payload [public-key clear-info payload-str]
(->> (merge {:enc-payload (encrypt public-key payload-str)}
clear-info)
(str)))
(defn make-msg
"Returns [msg-id msg], `msg` is formed for Web3.shh.post()"
[{:keys [from to ttl topics payload encrypt? public-key]
[{:keys [from to ttl topics payload encrypt? public-key clear-info]
:or {ttl syng-msg-ttl
topics []}}]
(let [msg-id (random/id)]
@ -68,7 +73,7 @@
(mapv from-ascii))
:payload (cond->> (merge payload {:msg-id msg-id})
true (str)
encrypt? (encrypt public-key)
encrypt? (encrypt-payload public-key clear-info)
true (from-ascii))}
from (assoc :from from)
to (assoc :to to))}))
@ -77,14 +82,16 @@
(defn listen
"Returns a filter which can be stopped with (stop-whisper-listener)"
[web3 msg-handler]
(let [topics [syng-app-topic]
shh (whisper web3)
filter (.filter shh (make-topics topics) (fn [error msg]
(if error
(invoke-user-handler :error {:error-msg error})
(msg-handler web3 msg))))]
(state/add-filter topics filter)))
([web3 msg-handler]
(listen web3 msg-handler {}))
([web3 msg-handler {:keys [topics] :as opts :or {topics []}}]
(let [topics (conj topics syng-app-topic)
shh (whisper web3)
filter (.filter shh (make-topics topics) (fn [error msg]
(if error
(invoke-user-handler :error {:error-msg error})
(msg-handler web3 msg))))]
(state/add-filter topics filter))))
(defn stop-listener [filter]
(.stopWatching filter))

View File

@ -30,8 +30,12 @@
(contains-key? [{:keys [m]} key]
(contains? @m key)))
(defonce state (atom {:group-id nil
:storage (map->MapStore {:m (atom {})})}))
(defonce state (atom {:group-id nil
:group-identities nil
:storage (map->MapStore {:m (atom {})})}))
(defn set-group-id! [group-id]
(swap! state assoc :group-id group-id))
(defn add-to-chat [element-id from content]
(let [chat-area (g/getElement element-id)
@ -39,6 +43,11 @@
chat (str chat (subs from 0 6) ": " content "\n")]
(f/setValue chat-area chat)))
(defn set-group-identities [identities]
(let [ids (s/join "\n" identities)]
(-> (g/getElement "to-identities")
(f/setValue ids))))
(defn start []
(let [rpc-url (-> (g/getElement "rpc-url")
(f/getValue))]
@ -48,8 +57,8 @@
:handler (fn [{:keys [event-type] :as event}]
(log/info "Event:" (clj->js event))
(case event-type
:new-msg (let [{:keys [from payload]} event
{content :content} payload]
:new-msg (let [{from :from
{content :content} :payload} event]
(add-to-chat "chat" from content))
:msg-acked (let [{:keys [msg-id]} event]
(add-to-chat "chat" ":" (str "Message " msg-id " was acked")))
@ -59,10 +68,15 @@
(f/setValue identity)))
:delivery-failed (let [{:keys [msg-id]} event]
(add-to-chat "chat" ":" (str "Delivery of message " msg-id " failed")))
:new-group-chat (let [{:keys [from group-id]} event]
:new-group-chat (let [{:keys [from group-id identities]} event]
(set-group-id! group-id)
(set-group-identities identities)
(add-to-chat "group-chat" ":" (str "Received group chat invitation from " from " for group-id: " group-id)))
:group-chat-invite-acked (let [{:keys [from group-id]} event]
(add-to-chat "group-chat" ":" (str "Received ACK for group chat invitation from " from " for group-id: " group-id)))
:new-group-msg (let [{from :from
{content :content} :payload} event]
(add-to-chat "group-chat" from content))
(add-to-chat "chat" ":" (str "Don't know how to handle " event-type))))})
(e/listen (-> (g/getElement "msg")
(goog.events.KeyHandler.))
@ -75,7 +89,18 @@
(f/getValue))]
(p/send-user-msg {:to to-identity
:content msg})
(add-to-chat "chat" (p/my-identity) msg)))))))
(add-to-chat "chat" (p/my-identity) msg)))))
(e/listen (-> (g/getElement "group-msg")
(goog.events.KeyHandler.))
key-handler-events/KEY
(fn [e]
(when (= (.-keyCode e) KeyCodes/ENTER)
(let [msg (-> (g/getElement "group-msg")
(f/getValue))
group-id (:group-id @state)]
(p/send-group-msg {:group-id group-id
:content msg})
(add-to-chat "group-chat" (p/my-identity) msg)))))))
(defn start-group-chat []
(let [identities (-> (g/getElement "to-identities")
@ -83,7 +108,7 @@
(s/split "\n"))]
(add-to-chat "group-chat" ":" (str "Starting group chat with " identities))
(let [group-id (p/start-group-chat identities)]
(swap! state assoc :group-id group-id))))
(set-group-id! group-id))))
(let [button (g/getElement "connect-button")]
(e/listen button EventType/CLICK