From 6cb3e69658471e2b21f28d803d97d9e2eaad65a9 Mon Sep 17 00:00:00 2001 From: alwxndr Date: Thu, 4 Aug 2016 15:34:30 +0300 Subject: [PATCH] discovery rework: updates to the way broadcasting works and lots of small fixes and changes --- examples/simple-whisper-chat/project.clj | 2 +- .../src/cljs/status_im/core.cljs | 8 +- project.clj | 2 +- src/cljs/status_im/protocol/api.cljs | 91 +++++---- src/cljs/status_im/protocol/discovery.cljs | 183 +++++++++--------- src/cljs/status_im/protocol/handler.cljs | 15 +- .../status_im/protocol/state/discovery.cljs | 19 +- src/cljs/status_im/protocol/state/state.cljs | 11 +- src/cljs/status_im/protocol/web3.cljs | 30 ++- 9 files changed, 199 insertions(+), 162 deletions(-) diff --git a/examples/simple-whisper-chat/project.clj b/examples/simple-whisper-chat/project.clj index 883c599..eb1eefb 100644 --- a/examples/simple-whisper-chat/project.clj +++ b/examples/simple-whisper-chat/project.clj @@ -30,7 +30,7 @@ :cljsbuild {:builds [{:id "dev" - :source-paths ["src/cljs" "protocol/src/cljs"] + :source-paths ["src/cljs" "../../src/cljs"] ;; If no code is to be run, set :figwheel true for continued automagical reloading :figwheel {:on-jsload "status-im.core/on-js-reload"} diff --git a/examples/simple-whisper-chat/src/cljs/status_im/core.cljs b/examples/simple-whisper-chat/src/cljs/status_im/core.cljs index ca6da8b..6737ec6 100644 --- a/examples/simple-whisper-chat/src/cljs/status_im/core.cljs +++ b/examples/simple-whisper-chat/src/cljs/status_im/core.cljs @@ -125,8 +125,8 @@ (add-to-chat "chat" ":" (str "Don't know how to handle " event-type))))}) (e/listen (-> (g/getElement "msg") (goog.events.KeyHandler.)) - key-handler-events/KEY - (fn [e] + key-handler-events/KEY + (fn [e] (when (= (.-keyCode e) KeyCodes/ENTER) (let [msg (-> (g/getElement "msg") (f/getValue)) @@ -137,8 +137,8 @@ (add-to-chat "chat" (p/my-identity) msg))))) (e/listen (-> (g/getElement "group-msg") (goog.events.KeyHandler.)) - key-handler-events/KEY - (fn [e] + key-handler-events/KEY + (fn [e] (when (= (.-keyCode e) KeyCodes/ENTER) (let [msg (-> (g/getElement "group-msg") (f/getValue)) diff --git a/project.clj b/project.clj index 5a75d07..d3fadd2 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject status-im/protocol "0.1.1" +(defproject status-im/protocol "0.1.3" :description "FIXME: write this!" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" diff --git a/src/cljs/status_im/protocol/api.cljs b/src/cljs/status_im/protocol/api.cljs index 39726ab..ec762ec 100644 --- a/src/cljs/status_im/protocol/api.cljs +++ b/src/cljs/status_im/protocol/api.cljs @@ -4,7 +4,7 @@ [status-im.protocol.state.state :as state :refer [set-storage set-handler set-connection - set-identity + set-account connection storage]] [status-im.protocol.state.delivery :refer [add-pending-message]] @@ -22,7 +22,9 @@ save-hashtags get-topics save-status - save-name]] + save-name + save-photo-path + discovery-topic]] [status-im.protocol.delivery :refer [start-delivery-loop]] [status-im.protocol.web3 :refer [listen make-msg @@ -32,8 +34,7 @@ add-identity stop-listener stop-watching-filters]] - [status-im.protocol.handler :refer [handle-incoming-whisper-msg] - :as handler] + [status-im.protocol.handler :refer [handle-incoming-whisper-msg] :as handler] [status-im.protocol.user-handler :refer [invoke-user-handler]] [status-im.utils.encryption :refer [new-keypair]] [status-im.protocol.group-chat :refer [send-group-msg @@ -41,12 +42,14 @@ group-add-participant-msg group-remove-participant-msg removed-from-group-msg]] - [status-im.protocol.discovery :refer [init-discovery - get-hashtag-topics - discovery-response-topic - discovery-search-topic + [status-im.protocol.discovery :refer [hashtags->topics + user-topic + discovery-topic discovery-search-message - broadcast-status]] + broadcast-status + broadcast-account-update + broadcast-online + do-periodically]] [status-im.protocol.defaults :refer [default-content-type]] [status-im.utils.logging :as log]) (:require-macros [cljs.core.async.macros :refer [go]])) @@ -54,9 +57,16 @@ (defn create-connection [ethereum-rpc-url] (make-web3 ethereum-rpc-url)) +(defn my-account [] + (state/my-account)) + (defn my-identity [] (state/my-identity)) +(defn send-online [] + (let [topics [[(user-topic (my-identity)) discovery-topic]]] + (broadcast-online topics))) + (defn init-protocol "Required [handler ethereum-rpc-url storage] Optional [identity - if not passed a new identity is created automatically @@ -83,23 +93,29 @@ " ([parameters] (init-protocol {:public-key "no-identity" :address "no-address"} parameters)) - ([{:keys [public-key] :as account} {:keys [handler ethereum-rpc-url storage identity active-group-ids]}] + ([account {:keys [handler ethereum-rpc-url storage active-group-ids]}] (when (seq (state/get-all-filters)) (stop-watching-filters)) (set-storage storage) (set-handler handler) (go - (let [connection (create-connection ethereum-rpc-url)] + (let [connection (create-connection ethereum-rpc-url) + topics (get-topics)] (set-connection connection) - (set-identity public-key) + (set-account account) (listen connection handle-incoming-whisper-msg) (start-delivery-loop) (doseq [group-id active-group-ids] - (listen connection handle-incoming-whisper-msg {:topics [group-id]})) - (init-discovery) - (listen connection handle-incoming-whisper-msg {:topics [discovery-response-topic]}) + (listen connection handle-incoming-whisper-msg {:topic [group-id]})) + (doseq [topic topics] + (listen connection handle-incoming-whisper-msg {:topic topic})) + (do-periodically (* 60 10 1000) send-online) (invoke-user-handler :initialized {:identity account}))))) +(defn watch-user [{:keys [whisper-identity]}] + (let [topic [(user-topic whisper-identity) discovery-topic]] + (listen (connection) handle-incoming-whisper-msg {:topic topic}))) + (defn send-user-msg [{:keys [to content msg-id]}] (let [{:keys [msg-id msg] :as new-msg} (make-msg {:from (state/my-identity) @@ -123,17 +139,17 @@ (start-group-chat identities nil)) ([identities group-name] (let [group-topic (random/id) - keypair (new-keypair) - store (storage) - connection (connection) + keypair (new-keypair) + store (storage) + connection (connection) my-identity (state/my-identity) - identities (-> (set identities) - (conj my-identity))] + identities (-> (set identities) + (conj my-identity))] (save-keypair store group-topic keypair) (save-identities store group-topic identities) (save-group-admin store group-topic my-identity) (save-group-name store group-topic group-name) - (listen connection handle-incoming-whisper-msg {:topics [group-topic]}) + (listen connection handle-incoming-whisper-msg {:topic [group-topic]}) (doseq [ident identities :when (not (= ident my-identity))] (let [{:keys [msg-id msg]} (init-group-chat-msg ident group-topic identities keypair group-name)] (add-pending-message msg-id msg {:internal? true}) @@ -143,14 +159,14 @@ (defn group-add-participant "Only call if you are the group-admin" [group-id new-peer-identity] - (let [store (storage) + (let [store (storage) my-identity (my-identity)] (if-not (group-admin? store group-id my-identity) (log/error "Called group-add-participant but not group admin, group-id:" group-id "my-identity:" my-identity) (let [connection (connection) identities (-> (get-identities store group-id) (conj new-peer-identity)) - keypair (get-keypair store group-id) + keypair (get-keypair store group-id) group-name (group-name store group-id)] (save-identities store group-id identities) (let [{:keys [msg-id msg]} (group-add-participant-msg new-peer-identity group-id group-name identities keypair)] @@ -164,14 +180,14 @@ (defn group-remove-participant "Only call if you are the group-admin" [group-id identity-to-remove] - (let [store (storage) + (let [store (storage) my-identity (my-identity)] (if-not (group-admin? store group-id my-identity) (log/error "Called group-remove-participant but not group admin, group-id:" group-id "my-identity:" my-identity) (let [connection (connection) identities (-> (get-identities store group-id) (disj identity-to-remove)) - keypair (new-keypair)] + keypair (new-keypair)] (save-identities store group-id identities) (save-keypair store group-id keypair) (doseq [ident identities :when (not (= ident my-identity))] @@ -183,31 +199,32 @@ (post-msg connection msg)))))) (defn leave-group-chat [group-id] - (let [store (storage) + (let [store (storage) my-identity (my-identity)] (send-group-msg {:group-id group-id :type :left-group :payload {:identity my-identity} :internal? true}) (remove-group-data store group-id) - (stop-listener group-id))) + (stop-listener [group-id]))) (defn stop-broadcasting-discover [] - (let [topics (get-topics)] - (doseq [topic topics] - (stop-listener topic)) - (save-hashtags []))) + (doseq [topic (get-topics)] + (stop-listener topic))) -(defn broadcast-discover-status [name status hashtags] +(defn broadcast-discover-status [{:keys [name photo-path]} status hashtags] (log/debug "Broadcasting status: " name status hashtags) - (let [topics (get-hashtag-topics hashtags)] + (let [topics (hashtags->topics hashtags)] (stop-broadcasting-discover) - (listen (connection) handle-incoming-whisper-msg {:topics topics}) + (doseq [topic topics] + (listen (connection) handle-incoming-whisper-msg {:topic topic})) (save-name name) + (save-photo-path photo-path) (save-topics topics) (save-hashtags hashtags) (save-status status) - (broadcast-status))) + (broadcast-status topics) + (broadcast-status [[(user-topic (my-identity)) discovery-topic]]))) (defn search-discover [hashtags] (let [{:keys [msg-id msg]} (discovery-search-message hashtags)] @@ -219,3 +236,7 @@ (defn send-seen [to message-id] (handler/send-seen (connection) to message-id)) + +(defn send-account-update [account] + (let [topics [[(user-topic (my-identity)) discovery-topic]]] + (broadcast-account-update topics account))) \ No newline at end of file diff --git a/src/cljs/status_im/protocol/discovery.cljs b/src/cljs/status_im/protocol/discovery.cljs index d8b34c7..db0cfc2 100644 --- a/src/cljs/status_im/protocol/discovery.cljs +++ b/src/cljs/status_im/protocol/discovery.cljs @@ -1,85 +1,106 @@ (ns status-im.protocol.discovery (:require [status-im.protocol.state.state :as state :refer [connection - storage]] + storage]] [status-im.protocol.state.delivery :refer [add-pending-message]] [status-im.protocol.state.discovery :refer [save-status - get-name - get-status - get-hashtags]] + get-name + get-photo-path + get-status + get-hashtags]] [status-im.protocol.user-handler :refer [invoke-user-handler]] - [status-im.utils.logging :as log] + [status-im.utils.logging :as log] [status-im.protocol.web3 :refer [make-msg - post-msg]])) + post-msg]] + [cljs-time.core :refer [now]] + [cljs-time.coerce :refer [to-long]] + [status-im.utils.random :as random])) -(def discovery-response-topic "status-discovery-responses") -(def discovery-search-topic "status-discovery-searches") -(def discovery-hashtag-topic "status-search-") -(def broadcast-interval 1800000) +(def discovery-topic "status-discovery") +(def discovery-user-topic "status-user-") +(def discovery-hashtag-topic "status-hashtag-") + +(def daily-broadcast-ttl (* 60 60 24)) +(def weekly-broadcast-ttl (* daily-broadcast-ttl 7)) +(def monthly-broadcast-ttl (* daily-broadcast-ttl 30)) (defn set-interval "Invoke the given function after and every delay milliseconds." [delay f] (js/setInterval f delay)) -(defn get-hashtag-topics - "Create listen topic from hastags." +(defn hashtags->topics + "Create listen topic from hashtags." [hashtags] - (map #(str discovery-hashtag-topic %) hashtags)) + (->> (distinct hashtags) + (sort) + (mapv #(str discovery-hashtag-topic %)) + (mapv #(vector % discovery-topic)))) -(defn discover-response-message - "Create discover response message." - ([payload] - (discover-response-message payload nil)) - ([payload to] - (let [data {:from (state/my-identity) - :topics [discovery-response-topic] - :payload payload} - _ (log/debug "Creating discover message using: " data)] - (->> (cond-> data - to (assoc :to to)) - (make-msg))))) +(defn user-topic + "Create listen topic for user identity" + [identity] + (str discovery-user-topic identity)) -(defn send-discover-message - "Send discover message to network." - [{:keys [payload to] :or {to nil}}] - (log/debug "Sending discover status: " payload to) - (let [{:keys [msg-id msg] :as new-msg} (discover-response-message payload to)] - (post-msg (connection) msg) - new-msg)) +(defn create-discover-message + "Create discovery message" + [topic payload ttl to msg-id] + (let [data {:msg-id msg-id + :from (state/my-identity) + :topics topic + :ttl ttl + :payload payload}] + (->> (cond-> data + to (assoc :to to)) + (make-msg)))) -(defn send-broadcast-status - "Broadcast discover message." - [to hashtags location] - (let [name (get-name) - status (get-status)] - (send-discover-message {:payload {:name name - :status status - :hashtags hashtags - :type :discover-response - :location location} - :to to}))) +(defn send-discover-messages + "Send discover messages for each topic" + [{:keys [topics payload ttl to] :as msg :or {ttl weekly-broadcast-ttl + to nil}}] + (let [msg-id (random/id)] + (log/debug (str "Sending discover status messages with msg-id " msg-id " for each topic: ") msg) + (doseq [topic topics] + (let [{:keys [msg]} (create-discover-message topic payload ttl to msg-id)] + (post-msg (connection) msg))))) (defn broadcast-status "Broadcast discover message if we have hashtags." - ([] - (broadcast-status nil)) - ([to] - (let [hashtags (get-hashtags)] - (when (pos? (count hashtags)) - (.getCurrentPosition (.-geolocation js/navigator) - #(send-broadcast-status to hashtags %) - #(send-broadcast-status to hashtags nil) - {:enableHighAccuracy false - :timeout 20000 - :maximumAge 1000}))))) + [topics] + (let [name (get-name) + photo-path (get-photo-path) + status (get-status) + hashtags (get-hashtags)] + (send-discover-messages {:topics topics + :payload {:name name + :photo-path photo-path + :status status + :hashtags hashtags + :type :discover-response}}))) -(defn init-discovery - "Initialize broadcasting discover message." - [] - (set-interval broadcast-interval broadcast-status)) +(defn broadcast-account-update + "Broadcast discover message if we have hashtags." + [topics account] + (send-discover-messages {:topics topics + :ttl monthly-broadcast-ttl + :payload {:account account + :type :contact-update}})) + +(defn broadcast-online + "Broadcast user's online presence" + [topics] + (send-discover-messages {:topics topics + :ttl daily-broadcast-ttl + :payload {:at (to-long (now)) + :type :contact-online}})) + +(defn do-periodically + "Do something periodically" + [interval func] + (func) + (set-interval interval func)) (defn discovery-search-message [hashtags] - (let [topics (get-hashtag-topics hashtags) + (let [topics (hashtags->topics hashtags) payload {:type :discovery-search :hashtags hashtags}] make-msg {:from (state/my-identity) @@ -92,50 +113,26 @@ (log/debug "Received discover-response message: " payload) (when (not (= (state/my-identity) from)) (invoke-user-handler :discover-response {:from from - :payload payload}))) + :payload payload}))) (defn handle-discovery-search "Handle discover-search messages." [web3 from payload] (log/debug "Received discover-search message: " payload) + ;;TODO (task #188): (broadcast-status from)) -(defn get-next-latitude [latitude] - (let [base-latitude (int latitude)] - (if (< base-latitude 90) - (inc base-latitude) - (dec base-latitude)))) +#_(comment -(defn get-next-longitude [longitude] - (let [base-longitude (int longitude)] - (if (< base-longitude 180) (inc base-longitude) -180))) + (get-next-latitude 40.5) -(defn discover-in-proximity [{:keys [latitude longitude]} hashtags] - (let [base-latitude (int latitude) - base-longitude (int longitude) - next-latitude (get-next-latitude latitude) - next-longitude (get-next-longitude longitude) - topics [(clojure.string/join "," [base-latitude base-longitude]) - (clojure.string/join "," [next-latitude base-longitude]) - (clojure.string/join "," [base-latitude next-longitude]) - (clojure.string/join "," [next-latitude next-longitude])] - payload {:type :discover - :hashtags hashtags}] - (make-msg {:from (state/my-identity) - :topics topics - :payload payload}))) + (get-next-latitude 90) -(comment + (get-next-longitude 40.5) - (get-next-latitude 40.5) + (get-next-longitude 180) - (get-next-latitude 90) + (discover-in-proximity {:latitude 90 :longitude 180} []) - (get-next-longitude 40.5) - - (get-next-longitude 180) - - (discover-in-proximity {:latitude 90 :longitude 180} []) - - (.getCurrentPosition (.-geolocation js/navigator) #(.log js/console %) #(.log js/console %) {:enableHighAccuracy true, :timeout 20000, :maximumAge 1000}) - ) + (.getCurrentPosition (.-geolocation js/navigator) #(.log js/console %) #(.log js/console %) {:enableHighAccuracy true, :timeout 20000, :maximumAge 1000}) + ) diff --git a/src/cljs/status_im/protocol/handler.cljs b/src/cljs/status_im/protocol/handler.cljs index e59458f..1c21975 100644 --- a/src/cljs/status_im/protocol/handler.cljs +++ b/src/cljs/status_im/protocol/handler.cljs @@ -79,7 +79,7 @@ (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]}) + (listen web3 handle-incoming-whisper-msg {:topic [group-topic]}) (save-keypair store group-topic keypair) (save-identities store group-topic identities) (save-group-admin store group-topic from) @@ -138,7 +138,7 @@ (send-ack web3 from msg-id) (when (group-member? store group-topic (state/my-identity)) (remove-group-data store group-topic) - (stop-listener group-topic) + (stop-listener [group-topic]) (invoke-user-handler :removed-from-group {:group-id group-topic :from from :msg-id msg-id}))) @@ -161,6 +161,15 @@ :left-group (handle-participant-left-group web3 from payload)) (log/debug "Could not decrypt group msg, possibly you've left the group."))) +(defn handle-contact-update [from payload] + (log/debug "Received contact-update message: " payload) + (invoke-user-handler :contact-update {:from from + :payload payload})) + +(defn handle-contact-online [from payload] + (invoke-user-handler :contact-online {:from from + :payload payload})) + (defn handle-incoming-whisper-msg [web3 msg] (log/info "Got whisper message:" msg) (let [{from :from @@ -183,6 +192,8 @@ :left-group (handle-group-msg web3 msg-type from payload) :discovery-search (handle-discovery-search web3 from payload) :discover-response (handle-discover-response web3 from payload) + :contact-update (handle-contact-update from payload) + :contact-online (handle-contact-online from payload) (if msg-type (log/debug "Undefined message type: " (name msg-type)) (log/debug "Nil message type")))) diff --git a/src/cljs/status_im/protocol/state/discovery.cljs b/src/cljs/status_im/protocol/state/discovery.cljs index 93c8002..047dfe1 100644 --- a/src/cljs/status_im/protocol/state/discovery.cljs +++ b/src/cljs/status_im/protocol/state/discovery.cljs @@ -1,17 +1,16 @@ (ns status-im.protocol.state.discovery (:require [status-im.protocol.state.storage :as s] - [status-im.protocol.state.state :as state] [status-im.protocol.state.state :as state :refer [set-storage - set-handler - set-connection - set-identity - connection - storage]])) + set-handler + set-connection + connection + storage]])) (def discovery-status "discovery-status") (def discovery-topics "discovery-topics") (def discovery-hashtags "discovery-hashtags") (def discovery-name "discovery-name") +(def discovery-photo-path "discovery-photo-path") (defn save-status [status] (let [store (storage)] @@ -45,3 +44,11 @@ (let [store (storage)] (s/get store discovery-name))) +(defn save-photo-path [photo-path] + (let [store (storage)] + (s/put store discovery-photo-path photo-path))) + +(defn get-photo-path [] + (let [store (storage)] + (s/get store discovery-photo-path))) + diff --git a/src/cljs/status_im/protocol/state/state.cljs b/src/cljs/status_im/protocol/state/state.cljs index 04f61da..f6d266f 100644 --- a/src/cljs/status_im/protocol/state/state.cljs +++ b/src/cljs/status_im/protocol/state/state.cljs @@ -5,7 +5,7 @@ :filters {} :delivery-queue #queue [] :external-handler nil - :identity nil + :account nil :connection nil :storage nil})) @@ -30,8 +30,8 @@ (defn set-handler [handler] (swap! state assoc :external-handler handler)) -(defn set-identity [identity] - (swap! state assoc :identity identity)) +(defn set-account [identity] + (swap! state assoc :account identity)) (defn set-connection [connection] (swap! state assoc :connection connection)) @@ -39,8 +39,11 @@ (defn connection [] (:connection @state)) +(defn my-account [] + (:account @state)) + (defn my-identity [] - (:identity @state)) + (get-in @state [:account :public-key])) (defn external-handler [] (:external-handler @state)) diff --git a/src/cljs/status_im/protocol/web3.cljs b/src/cljs/status_im/protocol/web3.cljs index 66445a8..4f47408 100644 --- a/src/cljs/status_im/protocol/web3.cljs +++ b/src/cljs/status_im/protocol/web3.cljs @@ -8,7 +8,7 @@ [status-im.protocol.user-handler :refer [invoke-user-handler]]) (:require-macros [cljs.core.async.macros :refer [go]])) -(def status-app-topic "STATUS-APP-CHAT-TOPIC") +(def status-app-topic "status-app") (def status-msg-ttl 100) (defn from-utf8 [s] @@ -112,28 +112,26 @@ from (assoc :from from) to (assoc :to to))})) - - (defn listen "Returns a filter which can be stopped with (stop-whisper-listener)" ([web3 msg-handler] (listen web3 msg-handler {})) - ([web3 msg-handler {:keys [topics] :as opts :or {topics []}}] - (let [topics (conj topics status-app-topic) + ([web3 msg-handler {:keys [topic] :or {topic []}}] + (let [topic (conj topic status-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)))) + filter (.filter shh (make-topics topic) (fn [error msg] + (if error + (invoke-user-handler :error {:error-msg error}) + (msg-handler web3 msg))))] + (log/debug "Listening to: " topic) + (state/add-filter topic filter)))) -(defn stop-listener [group-topic] - (let [topics (conj [group-topic] status-app-topic) - filter (state/get-filter topics)] +(defn stop-listener [topic] + (let [topic (conj topic status-app-topic) + filter (state/get-filter topic)] (when filter - (do - (.stopWatching filter) - (state/remove-filter topics))))) + (.stopWatching filter) + (state/remove-filter topic)))) (defn stop-watching-filters [] (doseq [filter (state/get-all-filters)]