init protocol

This commit is contained in:
michaelr 2016-03-23 21:05:42 +02:00
parent 7023540021
commit 61875cccc2
12 changed files with 315 additions and 14 deletions

View File

@ -15,7 +15,7 @@
(def logo-img (js/require "./images/cljs.png"))
(defn alert [title]
(.alert (.-Alert js/React) title))
(.alert (.-Alert js/React) title))
(defn app-root []
(let [greeting (subscribe [:get-greeting])]
@ -24,10 +24,11 @@
[text {:style {:font-size 30 :font-weight "100" :margin-bottom 20 :text-align "center"}} @greeting]
[image {:source logo-img
:style {:width 80 :height 80 :margin-bottom 30}}]
[touchable-highlight {:style {:background-color "#999" :padding 10 :border-radius 5}
[touchable-highlight {:style {:background-color "#999" :padding 10 :border-radius 5}
:on-press #(alert "HELLO!")}
[text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]])))
(defn init []
(dispatch-sync [:initialize-db])
(.registerComponent app-registry "SyngIm" #(r/reactify-component app-root)))
(dispatch-sync [:initialize-db])
(dispatch [:initialize-protocol])
(.registerComponent app-registry "SyngIm" #(r/reactify-component app-root)))

View File

@ -0,0 +1,5 @@
(ns syng-im.constants)
(def ethereum-rpc-url "http://localhost:8545")
(def text-content-type "text/plain")

View File

@ -5,4 +5,13 @@
(def schema {:greeting s/Str})
;; initial state of app-db
(def app-db {:greeting "Hello Clojure in iOS and Android!"})
(def app-db {:greeting "Hello Clojure in iOS and Android!"
:identity-password "replace-me-with-user-entered-password"})
(def protocol-initialized-path [:protocol-initialized])
(def simple-store-path [:simple-store])
(def identity-password-path [:identity-password])
(def current-chat-id-path [:chat :current-chat-id])
(defn arrived-message-path [chat-id]
[:chat chat-id :arrived-message-id])

View File

@ -2,7 +2,14 @@
(:require
[re-frame.core :refer [register-handler after]]
[schema.core :as s :include-macros true]
[syng-im.db :refer [app-db schema]]))
[syng-im.db :refer [app-db schema]]
[syng-im.protocol.api :refer [init-protocol]]
[syng-im.protocol.protocol-handler :refer [make-handler]]
[syng-im.models.protocol :refer [update-identity
set-initialized]]
[syng-im.models.messages :refer [save-message
new-message-arrived]]
[syng-im.utils.logging :as log]))
;; -- Middleware ------------------------------------------------------------
;;
@ -11,22 +18,39 @@
(defn check-and-throw
"throw an exception if db doesn't match the schema."
[a-schema db]
(if-let [problems (s/check a-schema db)]
(throw (js/Error. (str "schema check failed: " problems)))))
(if-let [problems (s/check a-schema db)]
(throw (js/Error. (str "schema check failed: " problems)))))
(def validate-schema-mw
(after (partial check-and-throw schema)))
;; -- Handlers --------------------------------------------------------------
(register-handler
:initialize-db
validate-schema-mw
(register-handler :initialize-db
(fn [_ _]
app-db))
(register-handler
:set-greeting
validate-schema-mw
;; -- Protocol --------------------------------------------------------------
(register-handler :initialize-protocol
(fn [db [_]]
(init-protocol (make-handler db))
db))
(register-handler :protocol-initialized
(fn [db [_ identity]]
(-> db
(update-identity identity)
(set-initialized true))))
(register-handler :received-msg
(fn [db [_ {chat-id :from
msg-id :msg-id :as msg}]]
(save-message chat-id msg)
(new-message-arrived db chat-id msg-id)))
;; -- Something --------------------------------------------------------------
(register-handler :set-greeting
(fn [db [_ value]]
(assoc db :greeting value)))

View File

@ -0,0 +1,8 @@
(ns syng-im.models.chat
(:require [syng-im.db :as db]))
(defn set-current-chat-id [db chat-id]
(assoc-in db db/current-chat-id-path chat-id))
(defn current-chat-id [db]
(get-in db db/current-chat-id-path))

View File

@ -0,0 +1,46 @@
(ns syng-im.models.messages
(:require [syng-im.persistence.realm :as r]
[cljs.reader :refer [read-string]]
[syng-im.utils.random :refer [timestamp]]
[syng-im.db :as db]))
(defn save-message [chat-id {:keys [from to msg-id content content-type outgoing] :or {outgoing false} :as msg}]
(when-not (r/exists? :msgs :msg-id msg-id)
(r/write
(fn []
(r/create :msgs {:chat-id chat-id
:msg-id msg-id
:from from
:to to
:content content
:content-type content-type
:outgoing outgoing
:timestamp (timestamp)} true)))))
(defn get-messages* [chat-id]
(-> (r/get-by-field :msgs :chat-id chat-id)
(r/sorted :timestamp :desc)
(r/page 0 10)))
(defn get-messages [chat-id]
(-> (get-messages* chat-id)
(js->clj :keywordize-keys true)))
(defn new-message-arrived [db chat-id msg-id]
(assoc-in db (db/arrived-message-path chat-id) msg-id))
(comment
(save-message "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154"
{:msg-id "153"
:content "hello!"
:content-type "text/plain"})
(get-messages* "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154")
(get-messages "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43")
(doseq [msg (get-messages* "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43")]
(r/delete msg))
)

View File

@ -0,0 +1,27 @@
(ns syng-im.models.protocol
(:require [cljs.reader :refer [read-string]]
[syng-im.protocol.state.storage :as s]
[syng-im.utils.encryption :refer [password-encrypt
password-decrypt]]
[syng-im.utils.types :refer [to-edn-string]]
[re-frame.db :refer [app-db]]
[syng-im.db :as db]
[syng-im.persistence.simple-kv-store :as kv]
[syng-im.utils.logging :as log]))
(defn set-initialized [db initialized?]
(assoc-in db db/protocol-initialized-path initialized?))
(defn update-identity [db identity]
(let [password (get-in db db/identity-password-path)
encrypted (->> (to-edn-string identity)
(password-encrypt password))]
(s/put kv/kv-store :identity encrypted)
(assoc db :user-identity identity)))
(defn stored-identity [db]
(let [encrypted (s/get kv/kv-store :identity)
password (get-in db db/identity-password-path)]
(when encrypted
(-> (password-decrypt password encrypted)
(read-string)))))

View File

@ -0,0 +1,94 @@
(ns syng-im.persistence.realm
(:require [cljs.reader :refer [read-string]]
[syng-im.utils.logging :as log]
[syng-im.utils.types :refer [to-string]])
(:refer-clojure :exclude [exists?]))
(set! js/Realm (js/require "realm"))
(def opts {:schema [{:name "Contact"
:properties {:phone-number "string"
:whisper-identity "string"
:name "string"
:photo-path "string"}}
{:name :kv-store
:primaryKey :key
:properties {:key "string"
:value "string"}}
{:name :msgs
:primaryKey :msg-id
:properties {:msg-id "string"
:from "string"
:to "string"
:content "string" ;; TODO make it ArrayBuffer
:content-type "string"
:timestamp "int"
:chat-id "string"
:outgoing "bool"}}]})
(def realm (js/Realm. (clj->js opts)))
(def schema-by-name (->> (:schema opts)
(mapv (fn [{:keys [name] :as schema}]
[name schema]))
(into {})))
(defn field-type [schema-name field]
(get-in schema-by-name [schema-name :properties field]))
(defn write [f]
(.write realm f))
(defn create
([schema-name obj]
(create schema-name obj false))
([schema-name obj update?]
(.create realm (to-string schema-name) (clj->js obj) update?)))
(defmulti to-query (fn [schema-name operator field value]
operator))
(defmethod to-query :eq [schema-name operator field value]
(let [value (to-string value)
query (str (name field) "=" (if (= "string" (field-type schema-name field))
(str "\"" value "\"")
value))
;_ (log/debug query)
]
query))
(defn get-by-field [schema-name field value]
(-> (.objects realm (name schema-name))
(.filtered (to-query schema-name :eq field value))))
(defn sorted [results field-name order]
(.sorted results (to-string field-name) (if (= order :asc)
false
true)))
(defn page [results from to]
(js/Array.prototype.slice.call results from to))
(defn single [result]
(-> (aget result 0)))
(defn single-cljs [result]
(some-> (aget result 0)
(js->clj :keywordize-keys true)))
(defn decode-value [{:keys [key value]}]
(read-string value))
(defn delete [obj]
(write (fn []
(.delete realm obj))))
(defn exists? [schema-name field value]
(> (.-length (get-by-field schema-name field value))
0))
(defn get-count [objs]
(.-length objs))
(defn get-list [schema-name]
(vals (js->clj (.objects realm schema-name) :keywordize-keys true)))

View File

@ -0,0 +1,24 @@
(ns syng-im.persistence.simple-kv-store
(:require [syng-im.protocol.state.storage :as st]
[syng-im.persistence.realm :as r]
[syng-im.utils.types :refer [to-edn-string]]))
(defrecord SimpleKvStore []
st/Storage
(put [_ key value]
(r/write
(fn []
(r/create :kv-store {:key key
:value (to-edn-string value)} true))))
(get [_ key]
(some-> (r/get-by-field :kv-store :key key)
(r/single-cljs)
(r/decode-value)))
(contains-key? [_ key]
(r/exists? :kv-store :key key))
(delete [_ key]
(-> (r/get-by-field :kv-store :key key)
(r/single)
(r/delete))))
(def kv-store (->SimpleKvStore))

View File

@ -0,0 +1,45 @@
(ns syng-im.protocol.protocol-handler
(:require [syng-im.utils.logging :as log]
[syng-im.constants :refer [ethereum-rpc-url]]
[re-frame.core :refer [dispatch]]
[syng-im.models.protocol :refer [stored-identity]]
[syng-im.persistence.simple-kv-store :as kv]))
(defn make-handler [db]
{:ethereum-rpc-url ethereum-rpc-url
:identity (stored-identity db)
:storage kv/kv-store
:handler (fn [{:keys [event-type] :as event}]
(log/info "Event:" (clj->js event))
(case event-type
:initialized (let [{:keys [identity]} event]
(dispatch [:protocol-initialized identity]))
:new-msg (let [{:keys [from to payload]} event]
(dispatch [:received-msg (assoc payload :from from :to to)]))
;:msg-acked (let [{:keys [msg-id]} event]
; (add-to-chat "chat" ":" (str "Message " msg-id " was acked")))
;: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 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))
;:group-new-participant (let [{:keys [group-id identity from]} event]
; (add-to-chat "group-chat" ":" (str (shorten from) " added " (shorten identity) " to group chat"))
; (add-identity-to-group-list identity))
;:group-removed-participant (let [{:keys [group-id identity from]} event]
; (add-to-chat "group-chat" ":" (str (shorten from) " removed " (shorten identity) " from group chat"))
; (remove-identity-from-group-list identity))
;:removed-from-group (let [{:keys [group-id from]} event]
; (add-to-chat "group-chat" ":" (str (shorten from) " removed you from group chat")))
;:participant-left-group (let [{:keys [group-id from]} event]
; (add-to-chat "group-chat" ":" (str (shorten from) " left group chat")))
;(add-to-chat "chat" ":" (str "Don't know how to handle " event-type))
(log/info "Don't know how to handle" event-type)
))})

View File

@ -0,0 +1,9 @@
(ns syng-im.utils.event
(:require [cljs.core.async :refer [<!]])
(:require-macros [cljs.core.async.macros :refer [go]]))
(defn handle-channel-events [chan handler]
(go (loop [[msg args] (<! chan)]
(when msg
(handler msg args)
(recur (<! chan))))))

View File

@ -0,0 +1,9 @@
(ns syng-im.utils.types)
(defn to-string [s]
(if (keyword? s)
(name s)
s))
(defn to-edn-string [value]
(with-out-str (pr value)))