parent
2c54a06399
commit
83d3ec8585
|
@ -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)))
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
(ns syng-im.constants)
|
||||
|
||||
(def ethereum-rpc-url "http://localhost:8545")
|
||||
|
||||
(def text-content-type "text/plain")
|
|
@ -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])
|
|
@ -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)))
|
|
@ -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))
|
|
@ -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))
|
||||
|
||||
)
|
|
@ -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)))))
|
|
@ -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)))
|
|
@ -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))
|
|
@ -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)
|
||||
))})
|
|
@ -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))))))
|
|
@ -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)))
|
Loading…
Reference in New Issue