id-kit / src / ardegazu / id / profile.cljs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
;; ported-from: src/profile.ts @ v1.1.0
;;
;; Cross-app profile: the small set of personal fields every suite app renders.
;; `hueOfPub` keeps the exact historical hash (h*31 + charCode over the pub) so
;; a person's color is stable everywhere and identical to what any peer
;; computes locally. A chosen `hue` overrides the derived one; a chosen `glyph`
;; overrides the first fingerprint emoji.
(ns ardegazu.id.profile
  (:refer-clojure :exclude [object?])
  (:require [ardegazu.id.identity :as identity]))

(defn- object?
  "Exactly TS's `typeof x === \"object\"` + truthiness gate (arrays pass)."
  [x]
  (and (some? x) (identical? "object" (js* "typeof ~{}" x))))

(def EMPTY-PROFILE (js-obj "name" "" "hue" nil "glyph" nil "lang" nil))

(def MAX-NAME-LEN 32)

(defn hue-of-pub
  "Same hash the suite always used for colors — over the identity pub."
  [pub-b64]
  (let [n (.-length pub-b64)]
    (loop [i 0 h 0]
      (if (< i n)
        (recur (inc i)
               (unsigned-bit-shift-right (+ (* h 31) (.charCodeAt pub-b64 i)) 0))
        (js-mod h 360)))))

(defn effective-hue
  "Effective hue for an identity: chosen hue if set, else derived."
  ([pub-b64] (effective-hue pub-b64 nil))
  ([pub-b64 profile]
   (let [hue (when (some? profile) (unchecked-get profile "hue"))]
     (if (and (number? hue) (>= hue 0) (< hue 360))
       (js/Math.floor hue)
       (hue-of-pub pub-b64)))))

(defn color-of-pub
  "The suite's standard chat-style color for an identity."
  ([pub-b64] (color-of-pub pub-b64 nil))
  ([pub-b64 profile]
   (str "hsl(" (effective-hue pub-b64 profile) " 45% 40%)")))

(defn- first-grapheme [s]
  (cond
    (or (nil? s) (identical? s "")) nil

    (and (exists? js/Intl) (js-in "Segmenter" js/Intl))
    (let [seg (.segment (js/Intl.Segmenter.) s)
          iter (.call (unchecked-get seg js/Symbol.iterator) seg)
          r (.next ^js iter)]
      (if (.-done ^js r) nil (unchecked-get (.-value ^js r) "segment")))

    :else
    (let [arr (js/Array.from s)]
      (if (pos? (.-length arr)) (aget arr 0) nil))))

(defn default-glyph
  "Effective glyph: the first fingerprint emoji (promise)."
  [pub-b64]
  (.then (identity/fingerprint-of pub-b64)
         (fn [fp]
           (let [g (first-grapheme (unchecked-get fp "emoji"))]
             (if (some? g) g "◆")))))

(defn clamp-profile
  "Clamp untrusted profile data (bridge messages, peer hellos) to a safe shape.
   Never throws; unusable fields collapse to their null/empty defaults."
  [p]
  (let [src (if (object? p) p (js-obj))
        name-v (let [v (unchecked-get src "name")]
                 (if (string? v)
                   (.join (.slice (js/Array.from (.trim v)) 0 MAX-NAME-LEN) "")
                   ""))
        hue-v (let [v (unchecked-get src "hue")]
                (if (and (number? v) (js/Number.isFinite v) (>= v 0) (< v 360))
                  (js/Math.floor v)
                  nil))
        glyph-v (let [v (unchecked-get src "glyph")]
                  (if (string? v) (first-grapheme (.trim v)) nil))
        lang-v (let [v (unchecked-get src "lang")]
                 (if (and (string? v) (.test #"^[a-z]{2}$" v)) v nil))]
    (js-obj "name" name-v "hue" hue-v "glyph" glyph-v "lang" lang-v)))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/id-kit.git