<commit_before>(ns status-im.protocol.listeners
  (:require [cljs.reader :as r]
            [status-im.protocol.ack :as ack]
            [status-im.protocol.web3.utils :as u]
            [status-im.protocol.encryption :as e]
            [taoensso.timbre :as log]
            [status-im.utils.hex :as i]))

(defn create-error [description]
  (log/debug :parse-payload-error description)
  {:error description})

(defn- parse-payload [payload]
  (log/debug :parse-payload)
  (try
    ;; todo figure why we have to call to-utf8 twice
    (let [payload'   (u/to-utf8 payload)
          payload''  (r/read-string payload')
          payload''' (if (map? payload'')
                       payload''
                       (r/read-string (u/to-utf8 payload')))]
      (if (map? payload''')
        {:payload payload'''}
        (create-error (str "Invalid payload type " (type payload''')))))
    (catch :default err
      (create-error err))))

(defn- decrypt [key content]
  (try
    {:content (r/read-string (e/decrypt key content))}
    (catch :default err
      (log/debug :decrypt-error err)
      {:error err})))

(defn- parse-content [key {:keys [content]} was-encrypted?]
  (log/debug :parse-content
         "Key exists:" (not (nil? key))
         "Content exists:" (not (nil? content)))
  (if (and (not was-encrypted?) key content)
    (decrypt key content)
    {:content content}))

(defn message-listener
  [{:keys [web3 identity callback keypair]}]
  (fn [error js-message]
    ;; todo handle error
    (when error
      (log/debug :listener-error error))
    (when-not error
      (log/debug :message-received (js->clj js-message))
      (let [{:keys [sig payload recipientPublicKey] :as message}
            (js->clj js-message :keywordize-keys true)

            {{:keys [type ack?] :as payload'} :payload
             payload-error                    :error}
            (parse-payload payload)]
        (when (and (not payload-error)
                   (or (not= (i/normalize-hex identity)
                             (i/normalize-hex sig))
                       ;; allow user to receive his own discoveries
                       (= type :discover)))
          (let [{:keys [content error]} (parse-content (:private keypair)
                                                       payload'
                                                       (and (not= "0x0" recipientPublicKey)
                                                            (not= "" recipientPublicKey)
                                                            (not (nil? recipientPublicKey))))]
            (if error
              (log/debug :failed-to-handle-message error)
              (let [payload'' (assoc payload' :content content)
                    message'  (assoc message :payload payload''
                                             :to recipientPublicKey
                                             :from sig)]
                (callback (if ack? :ack type) message')
                (ack/check-ack! web3 sig payload'' identity)))))))))
<commit_msg>feature #1805 refactored protocol listeners<commit_after>(ns status-im.protocol.listeners
  (:require [cljs.reader :as r]
            [status-im.protocol.ack :as ack]
            [status-im.protocol.web3.utils :as u]
            [status-im.protocol.encryption :as e]
            [taoensso.timbre :as log]
            [status-im.utils.hex :as i]))

(defn empty-public-key? [public-key]
  (or (= "0x0" public-key)
      (= "" public-key)
      (nil? public-key)))

(defn create-error [step description]
  (when (not= description :silent)
    (log/debug step description))
  {:error description})

(defn init-scope [js-error js-message options]
  (if js-error
    (create-error :init-scope-error (-> js-error js->clj str))
    {:message (js->clj js-message :keywordize-keys true)
     :options options}))

(defn parse-payload [{:keys [message error options] :as scope}]
  (log/debug :parse-payload)
  (if error
    scope
    (try
      ;; todo figure why we sometimes have to call to-utf8 twice and sometimes only once
      (let [payload    (:payload message)
            payload'   (u/to-utf8 payload)
            payload''  (r/read-string payload')
            payload''' (if (map? payload'')
                         payload''
                         (r/read-string (u/to-utf8 payload')))]
        (if (map? payload''')
          {:message (assoc message :payload payload''')
           :options options}
          (create-error :parse-payload-error (str "Invalid payload type " (type payload''')))))
      (catch :default err
        (create-error :parse-payload-error  err)))))

(defn filter-messages-from-same-user [{:keys [message error options] :as scope}]
  (if error
    scope
    (if (or (not= (i/normalize-hex (:identity options))
                  (i/normalize-hex (:sig message)))
            ;; allow user to receive his own discoveries
            (= type :discover))
      scope
      (create-error :filter-messages-error :silent))))

(defn parse-content [{:keys [message error options] :as scope}]
  (if error
    scope
    (try
      (let [to             (:recipientPublicKey message)
            from           (:sig message)
            key            (get-in options [:keypair :private])
            raw-content    (get-in message [:payload :content])
            encrypted?     (and (empty-public-key? to) key raw-content)
            content        (if encrypted?
                             (r/read-string (e/decrypt key raw-content))
                             raw-content)]
        (log/debug :parse-content
                   "Key exists:" (not (nil? key))
                   "Content exists:" (not (nil? raw-content)))
        {:message (-> message
                      (assoc-in [:payload :content] content)
                      (assoc :to to
                             :from from))
         :options options})
      (catch :default err
        (create-error :parse-content-error err)))))

(defn handle-message [{:keys [message error options] :as scope}]
  (if error
    scope
    (let [{:keys [web3 identity callback]} options
          {:keys [payload sig]}            message
          ack?                            (get-in message [:payload :ack?])]
      (log/debug :handle-message message)
      (callback (if ack? :ack (:type payload)) message)
      (ack/check-ack! web3 sig payload identity))))

(defn message-listener
  "Valid options are: web3, identity, callback, keypair"
  [options]
  (fn [js-error js-message]
    (-> (init-scope js-error js-message options)
        parse-payload
        filter-messages-from-same-user
        parse-content
        handle-message)))

{'fraction_non_alphanumeric': 0.12951581472043172, 'fraction_numerical': 0.0031479538300104933, 'mean_word_length': 1.7753743760399334, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '806499', 'n_tokens_mistral': 1897, 'n_tokens_neox': 1851, 'n_words': 540}