PageSourceSearch

https://xagent.id/assets/connect-source.js

js xagent.id collected 2026-10-05 06:32:48 UTC 6,753 bytes, 159 lines download raw bytes

1/* Guided source connection.
2 *
3 * Reading "connect the log source that already records AI crawler requests" and
4 * then hand-assembling a Cloudflare GraphQL call is not a flow a customer
5 * completes. This walks it: pick a source, say which account it is, copy the
6 * exact request we built, paste what came back, see what was extracted, commit.
7 *
8 * The copy-and-paste middle is not a shortcut we took. This service never holds
9 * a customer credential, so the request has to be run by the customer against
10 * their own account. Making that explicit is the honest shape of the flow, and
11 * the dialog says so rather than leaving the user to wonder why.
12 */
13(function attachConnectSource(root) {
14  "use strict";
15
16  const FIELDS = {
17    cloudflare_a1: [
18      {name: "zone_id", type: "text", label: ["Cloudflare zone id", "Cloudflare zone id"],
19       hint: ["Found on the zone overview page in your Cloudflare dashboard.",
20              "在 Cloudflare 控制台的 zone 總覽頁可以找到。"]},
21    ],
22    ga4_b1: [
23      {name: "property_id", type: "text", label: ["GA4 property id", "GA4 property id"],
24       hint: ["The numeric id, without the properties/ prefix.", "純數字 id,不用加 properties/ 前綴。"]},
25    ],
26    crm_b1: [
27      {name: "supplied_response", type: "json", label: ["Conversion rows", "轉換資料"],
28       hint: ["Paste the exported rows as JSON. Each row keeps its order receipt.",
29              "把匯出的資料以 JSON 貼上。每一列都要保留訂單收據。"]},
30    ],
31    a2_provider_response: [
32      {name: "provider", type: "text", label: ["Provider", "供應商"],
33       hint: ["Which engine produced the answer.", "這個回答是哪個引擎產生的。"]},
34      {name: "prompt_id", type: "text", label: ["Prompt id", "Prompt id"],
35       hint: ["The prompt this answer came from.", "這個回答對應的 prompt。"]},
36      {name: "supplied_response", type: "json", label: ["The answer", "回答內容"],
37       hint: ["Paste the engine's response as JSON.", "把引擎的回應以 JSON 貼上。"]},
38    ],
39    a2_probe_run: [],
40  };
41
42  const NEEDS_WINDOW = new Set(["cloudflare_a1", "ga4_b1", "crm_b1"]);
43  const PLAN_FIRST = new Set(["cloudflare_a1", "ga4_b1"]);
44
45  function createConnectSource(options) {
46    const request = options && options.request;
47    const localized = (options && options.localized) || ((en) => en);
48    if (typeof request !== "function") throw new TypeError("connect_source_request_required");
49
50    const state = {host: "", connector: "", values: {}, plan: null, evidence: null};
51
52    function reset(host, connector) {
53      state.host = String(host || "");
54      state.connector = String(connector || "");
55      state.values = {};
56      state.plan = null;
57      state.evidence = null;
58    }
59
60    function fieldsFor(connector) {
61      return FIELDS[connector] || [];
62    }
63
64    function parseValue(field, raw) {
65      if (field.type !== "json") return String(raw || "").trim();
66      const text = String(raw || "").trim();
67      if (!text) throw new Error("value_required");
68      try {
69        return JSON.parse(text);
70      } catch (_error) {
71        const error = new Error("invalid_json");
72        error.field = field.name;
73        throw error;
74      }
75    }
76
77    function collect(form) {
78      const values = {};
79      for (const field of fieldsFor(state.connector)) {
80        values[field.name] = parseValue(field, form[field.name]);
81      }
82      if (NEEDS_WINDOW.has(state.connector)) {
83        const start = String(form.window_start || "").trim();
84        const end = String(form.window_end || "").trim();
85        if (!start || !end) throw new Error("window_required");
86        values.window = {start, end};
87      }
88      return values;
89    }
90
91    function body(extra) {
92      return {url: `https://${state.host}`, connector: state.connector, ...state.values, ...(extra || {})};
93    }
94
95    async function plan(form) {
96      state.values = collect(form);
97      if (!PLAN_FIRST.has(state.connector)) return null;
98      const result = await request("/api/dashboard/connector/plan", {method: "POST", body: body()});
99      state.plan = result && result.plan;
100      return state.plan;
101    }
102
103    async function normalize(pastedResponse) {
104      const supplied = pastedResponse === undefined
105        ? state.values.supplied_response
106        : parseValue({name: "supplied_response", type: "json"}, pastedResponse);
107      const result = await request("/api/dashboard/connector/normalize", {
108        method: "POST", body: body({supplied_response: supplied}),
109      });
110      state.evidence = result && result.evidence;
111      return state.evidence;
112    }
113
114    async function submit() {
115      if (!state.evidence) throw new Error("nothing_to_submit");
116      return request("/api/dashboard/connector/submit", {
117        method: "POST",
118        body: {url: `https://${state.host}`, kind: "connector", evidence: state.evidence},
119      });
120    }
121
122    /* What the copied request actually is. Kept as text rather than a fetch the
123       page could run, because running it needs the customer's credential and
124       this page must never see one. */
125    function requestPreview() {
126      const p = state.plan;
127      if (!p) return "";
128      if (p.query) {
129        return `POST ${p.endpoint}\nAuthorization: Bearer <your Cloudflare API token>\nContent-Type: application/json\n\n${JSON.stringify({query: p.query}, null, 2)}`;
130      }
131      return `${p.method || "POST"} ${p.endpoint}\nAuthorization: Bearer <your access token>\nContent-Type: application/json\n\n${JSON.stringify(p.body || {}, null, 2)}`;
132    }
133
134    function summary() {
135      const e = state.evidence;
136      if (!e || typeof e !== "object") return [];
137      const rows = [];
138      const push = (label, value) => {
139        if (value !== undefined && value !== null && value !== "") rows.push({label, value});
140      };
141      push(localized("Source", "來源"), e.provider || e.source || state.connector);
142      push(localized("Window", "期間"), e.window ? `${e.window.start || "?"} → ${e.window.end || "?"}` : "");
143      push(localized("Observed rows", "觀測筆數"), e.observed ?? e.total ?? e.rows);
144      push(localized("Receipt", "收據"), e.receipt_hash || e.content_hash);
145      return rows;
146    }
147
148    return {
149      reset, fieldsFor, plan, normalize, submit, requestPreview, summary,
150      needsPlan: (connector) => PLAN_FIRST.has(connector),
151      needsWindow: (connector) => NEEDS_WINDOW.has(connector),
152      state: () => ({...state}),
153    };
154  }
155
156  const api = {createConnectSource, FIELDS, NEEDS_WINDOW, PLAN_FIRST};
157  if (typeof module === "object" && module.exports) module.exports = api;
158  else root.AgentIdConnectSource = api;
159})(typeof globalThis === "object" ? globalThis : this);

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.