// ---
// tags: lytics, javascript
// crystal-type: source
// crystal-domain: cyber
// ---
// lytics loader โ€” the browser glue the wasm core cannot reach: identity
// persistence, the attention sensor, SPA navigation, transport. all crypto
// (keys, signing, pow) lives in the wasm core; this file only observes and
// ships. spec: lytics/specs/README.md, the attention model.

import init, { Tracker, generate_entropy } from "./lytics_core.js";

// type=module scripts have document.currentScript === null โ€” resolve our tag.
const script =
  document.currentScript ||
  document.querySelector('script[src*="loader.js"][data-endpoint]') ||
  document.querySelector('script[type="module"][src*="/lytics/tracker/"]') ||
  document.querySelector('script[type="module"][src*="loader.js"]');
// default /lytics when embedded on cyberstates (same-origin subpath)
const ENDPOINT = (script?.dataset?.endpoint || "/lytics").replace(/\/$/, "");
const DOMAIN =
  script?.dataset?.domain || location.hostname.replace(/^www\./, "");
const HRP = script?.dataset?.hrp || "lytics";
const HEARTBEAT_MS = 15000; // instrument granularity โ€” bounds attention lost to a killed tab
const IDLE_MS = 5000; // input silence pauses the attention clock

const KEY = `lytics:entropy:${DOMAIN}`;
let tracker, eventTarget, enrollTarget, ready;
const queue = []; // events captured before the core is live

// โ”€โ”€ identity: 32-byte entropy lives in localStorage, per-domain โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function loadOrCreateEntropy() {
  let e = localStorage.getItem(KEY);
  if (!e) {
    e = generate_entropy(); // hex, no wordlist touched
    localStorage.setItem(KEY, e);
  }
  return e;
}
// export/import as a human-readable 24-word backup loads the wordlist lazily
// โ€” a rare, deliberate action, never on the signing path. `words.js` wraps
// @scure/bip39 (entropy <-> phrase); shipped separately, imported on demand.
window.lytics = window.lytics || {};
window.lytics.exportEntropy = () => localStorage.getItem(KEY);
window.lytics.exportPhrase = async () => {
  const { entropyToPhrase } = await import("./words.js");
  return entropyToPhrase(localStorage.getItem(KEY));
};
window.lytics.importPhrase = async (phrase) => {
  const { phraseToEntropy } = await import("./words.js");
  localStorage.setItem(KEY, phraseToEntropy(phrase));
  location.reload();
};
window.lytics.forget = () => {
  localStorage.removeItem(KEY);
};

// โ”€โ”€ transport โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
async function difficulty() {
  const neuron = tracker.neuron;
  const r = await fetch(`${ENDPOINT}/api/difficulty?neuron=${neuron}`);
  const d = await r.json();
  eventTarget = BigInt(d.event_target);
  enrollTarget = BigInt(d.enroll_target);
  return d.enrolled;
}
let enrolled = false;
function currentTarget() {
  return enrolled ? eventTarget : enrollTarget;
}
async function send(spec) {
  const target = currentTarget();
  // wasm signs + pow's from explicit args and returns the ready-to-POST JSON
  // (no JSON parse in the wasm; nothing to stringify here)
  const json = tracker.build_event(
    spec.kind,
    spec.pathname,
    spec.navigation ?? undefined,
    spec.referrer ?? undefined,
    spec.attention_ms ?? undefined,
    spec.scroll_depth ?? undefined,
    spec.agent_name ?? undefined,
    spec.agent_operator ?? undefined,
    spec.timestamp,
    target,
  );
  const r = await fetch(`${ENDPOINT}/api/event`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: json,
    keepalive: true,
  });
  if (r.status === 202) enrolled = true; // first accept enrolls the neuron
  return r.status;
}

// โ”€โ”€ the attention sensor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// integrate time while the page is visible AND the neuron is active. `counting`
// is the single truth of whether the clock runs; every stretch is accrued
// before the clock stops, so no visible time is ever lost to a transition.
let attentionMs = 0,
  scrollDepth = 0,
  lastTick = 0,
  counting = false,
  lastInput = 0;
function now() {
  return Date.now();
}
function startCounting() {
  if (!counting) {
    counting = true;
    lastTick = now();
  }
}
function stopCounting() {
  accrue();
  counting = false;
}
function accrue() {
  if (!counting) return;
  const t = now();
  attentionMs += t - lastTick;
  lastTick = t;
  if (t - lastInput > IDLE_MS) counting = false; // idle โ†’ pause the clock
}
function markActive() {
  lastInput = now();
  if (document.visibilityState === "visible") startCounting();
}
function trackScroll() {
  const h = document.documentElement;
  const denom = h.scrollHeight - h.clientHeight || 1;
  const pct = Math.min(100, Math.round((h.scrollTop / denom) * 100));
  if (pct > scrollDepth) scrollDepth = pct;
}
async function flushAttention() {
  accrue();
  if (attentionMs < 1000) return;
  const ms = attentionMs;
  attentionMs = 0;
  await afterReady(() =>
    send({
      kind: "attention",
      pathname: location.pathname,
      attention_ms: ms,
      scroll_depth: scrollDepth,
      timestamp: now(),
    }),
  );
}

// โ”€โ”€ pageview + SPA โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
let firstView = true;
function pageview() {
  const nav = firstView
    ? document.referrer && !document.referrer.includes(DOMAIN)
      ? "external"
      : "direct"
    : "internal";
  firstView = false;
  scrollDepth = 0;
  attentionMs = 0;
  markActive();
  const spec = {
    kind: "pageview",
    pathname: location.pathname,
    navigation: nav,
    referrer: document.referrer || null,
    timestamp: now(),
  };
  afterReady(() => send(spec));
}
function afterReady(fn) {
  if (ready) return fn();
  queue.push(fn); // captured before the core loaded โ€” signed retroactively
}
function hookHistory() {
  for (const m of ["pushState", "replaceState"]) {
    const orig = history[m];
    history[m] = function () {
      const r = orig.apply(this, arguments);
      onRoute();
      return r;
    };
  }
  addEventListener("popstate", onRoute);
}
let lastPath = location.pathname;
function flushAndView() {
  flushAttention().then(pageview);
}
function onRoute() {
  if (location.pathname === lastPath) return;
  lastPath = location.pathname;
  flushAndView();
}

// โ”€โ”€ boot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
addEventListener("scroll", trackScroll, { passive: true });
for (const e of ["mousemove", "keydown", "click", "scroll", "touchstart"]) {
  addEventListener(e, markActive, { passive: true });
}
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    stopCounting();
    flushAttention();
  } else {
    markActive();
  }
});
addEventListener("pagehide", () => {
  stopCounting();
  flushAttention();
});
addEventListener("blur", accrue);
setInterval(flushAttention, HEARTBEAT_MS);

// capture the first pageview immediately, then bring the core up
pageview();
hookHistory();
(async () => {
  await init();
  tracker = new Tracker(loadOrCreateEntropy(), DOMAIN, HRP);
  enrolled = await difficulty();
  ready = true;
  window.lytics.neuron = tracker.neuron;
  while (queue.length) {
    const fn = queue.shift();
    await fn();
  }
})().catch((e) => console.error("lytics:", e));

Homonyms

bootloader/space-pussy/ts/src/components/loader/loader.js

Graph