PageSourceSearch

https://xagent.id/assets/dashboard-report-api.js?v=tenant-20260920

js xagent.id collected 2026-10-05 06:32:50 UTC 3,535 bytes, 84 lines download raw bytes

1/**
2 * dashboard-report-api.js — Dashboard 報告頁資料層(Stage 1: 資料流)
3 *
4 * 與 `dashboard-client.js` 的既定 bundle contract(browser client 契約測試
5 * 鎖定的 mode/sites/data/connectors/tasks/billing/decision 序列)分離:
6 * 本檔案只抓取「報告區塊」所需的補充資料,絕不觸碰 bundle 既有的端點順序。
7 * 掛入點:`dashboard.html` 在 dashboard-client.js 之後載入本檔;
8 * 初始化由 `dashboard-report-stage1.js` 的 root.AgentIdReportStage1.init() 驅動。
9 *
10 * 設計契約(與 docs/dashboard-frontend-architecture-2026-08-15.md 一致):
11 * 1. 所有 fetch 帶固定 timeout(6s),失敗一律常態化為 { error: string },
12 *    絕不拋出、絕不讓區塊級缺失變成整頁錯誤(pending_not_zero 政策)。
13 * 2. JSON 解析失敗也常態化為 invalid_json,不丟出 SyntaxError。
14 * 3. 回傳值結構只做最小存在性驗證,不做全 schema 校驗(既有端點欄位以
15 *    server.py _build_customer_evidence_dashboard 實測值為準)。
16 */
17(() => {
18  "use strict";
19
20  const REPORT_API_TIMEOUT_MS = 6000;
21
22  const parseJsonSafe = (text) => {
23    try { return JSON.parse(text); }
24    catch (_) { return null; }
25  };
26
27  /**
28   * 安全 JSON fetch:{ ok:true, data } | { ok:false, error, url? }
29   * 失敗原因分三類:http_<status> / invalid_json / network_unreachable。
30   */
31  const fetchJson = async (url, options = {}) => {
32    const ctrl = new AbortController();
33    const timer = window.setTimeout(() => ctrl.abort(), REPORT_API_TIMEOUT_MS);
34    try {
35      const res = await window.fetch(url, Object.assign({}, options, { signal: ctrl.signal }));
36      if (!res.ok) return { ok: false, error: `http_${res.status}`, url };
37      const text = await res.text();
38      const data = parseJsonSafe(text);
39      return data == null ? { ok: false, error: "invalid_json", url } : { ok: true, data };
40    } catch (_) {
41      return { ok: false, error: "network_unreachable", url };
42    } finally {
43      window.clearTimeout(timer);
44    }
45  };
46
47  /**
48   * 主報告 payload 僅由 page coordinator 發布。公開 demo 與登入後 bundle
49   * 都沿用原資料,不另發請求;尚未完成授權或清除狀態時保持 unavailable。
50   */
51  // The page coordinator is the only payload producer. Stages never initiate
52  // a demo request or infer authority from a URL/body/visible DOM label.
53  let context = null;
54  const publish = (mode, host, data) => {
55    const normalize = window.AgentIdDashboardClient.normalizeHost;
56    if (!["customer", "demo"].includes(mode) || !normalize(host)
57        || normalize(data?.customer?.host) !== normalize(host)) {
58      throw new Error("dashboard_report_subject_mismatch");
59    }
60    context = {mode, host: normalize(host), data};
61    window.dispatchEvent(new Event("dashboard-report-change"));
62  };
63  const clear = () => {
64    context = null;
65    window.dispatchEvent(new Event("dashboard-report-change"));
66  };
67  const current = () => context;
68  const fetchReportPayload = async () => context
69    ? {ok: true, data: context.data}
70    : {ok: false, error: "dashboard_report_not_authorized"};
71
72  /**
73   * Common Crawl 語料存在查詢:需 url= 參數,失敗常態化。
74   */
75  const fetchCommonCrawl = async (url) =>
76    fetchJson(`/api/agent/common_crawl_presence?url=${window.encodeURIComponent(url)}`);
77
78  window.AgentIdReportApi = Object.freeze({
79    fetchJson,
80    fetchReportPayload,
81    fetchCommonCrawl,
82    publish, clear, current,
83  });
84})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.