;; ported-from: src/node/identity.ts @ v1.3.0
;;
;; Suite identity for a headless peer — the Node counterpart of the games'
;; id/boot.ts. No iframe bridge: the 43-char base64url seed IS the identity,
;; held wherever the host process keeps secrets (file by default). A browser can
;; adopt the same identity by importing the seed on the hub.
;;
;; Vendor retirement: `Identity` and the Profile shape now come from the real
;; ardegazu-id-kit instead of src/vendor/id — compiled from its CLJS sources on
;; this build's classpath (see deps.edn), so the whole build shares exactly ONE
;; Identity class and `instanceof` holds everywhere.
(ns ardegazu.peer.node.identity
(:require [ardegazu.id.identity :as id-identity]
["node:fs" :refer (mkdirSync readFileSync writeFileSync)]
["node:path" :refer (dirname)]
[shadow.cljs.modern :refer (js-await)]))
(def ^:private SEED-RE #"^[A-Za-z0-9_-]{43}$")
(defn load-identity
"opts = #js {:seedFile? :seed? :name? :hue? :glyph?}
Resolves to #js {:identity :seed :profile :netIdentity}."
[opts]
(-> (js/Promise.resolve nil)
(.then
(fn [_]
(let [explicit (let [s (unchecked-get opts "seed")]
(if ^boolean (js* "~{} == null" s) nil s))
seed-file (unchecked-get opts "seedFile")]
(when (and (some? explicit) (not (.test SEED-RE explicit)))
(throw (js/Error. "invalid suite seed (want 43 chars of base64url)")))
(let [seed
(if (or (some? explicit) (not ^boolean (js* "!!(~{})" seed-file)))
explicit
(try
(let [read (.trim (readFileSync seed-file "utf8"))]
(when-not (.test SEED-RE read)
(throw (js/Error. (str "seed file " seed-file
" is not a valid suite seed"))))
read)
(catch :default err
(if-not (identical? "ENOENT" (unchecked-get err "code"))
(throw err)
(let [fresh (id-identity/new-seed)]
(mkdirSync (dirname seed-file) (js-obj "recursive" true))
(writeFileSync seed-file (str fresh "\n") (js-obj "mode" 0x180)) ; 0o600
fresh)))))]
(when-not ^boolean (js* "!!(~{})" seed)
(throw (js/Error. "no seed: pass seed or seedFile")))
(js-await [identity (id-identity/from-seed-impl seed)]
(let [hue (unchecked-get opts "hue")
glyph (unchecked-get opts "glyph")
nm (unchecked-get opts "name")
profile (js-obj "name" (if ^boolean (js* "~{} == null" nm) "" nm)
"hue" (if (identical? "number" (js* "typeof ~{}" hue)) hue nil)
"glyph" (if ^boolean (js* "~{} == null" glyph) nil glyph)
"lang" nil)]
(js-obj
"identity" identity
"seed" seed
"profile" profile
;; Ready-to-pass identity block for createNet (per-app salt).
"netIdentity"
(fn [app-salt]
(js-obj "pub" (unchecked-get identity "publicKeyB64")
"sign" (fn [room-id my-peer-id]
(.assert ^js identity app-salt room-id my-peer-id))
;; `profile.name || undefined`
"name" (let [n (unchecked-get profile "name")]
(if ^boolean (js* "!!(~{})" n) n js/undefined))
"hue" (unchecked-get profile "hue")
"glyph" (unchecked-get profile "glyph"))))))))))))