@syntrologie/adapt-chatbot 2.8.0-canary.256 → 2.8.0-canary.258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,20 +11,22 @@
11
11
  * `window.__SYNTRO_CONFIG__.token` (set by runtime-config.js) — we read
12
12
  * it at mount time and forward it via `Authorization: Bearer` header.
13
13
  *
14
- * 2. Cloudflare Turnstile bot-check token (two-phase). When
15
- * `TURNSTILE_SITEKEY` is set:
16
- * a. Try invisible first passive fingerprints, no UI. Clean
17
- * traffic resolves in milliseconds. Token forwarded via
18
- * `CF-Turnstile-Token`.
19
- * b. If CF declines to grant silently (datacenter IP, automation
20
- * signals, strict-privacy browser), render a visible managed
21
- * widget inside the chat container so CF can present the
22
- * "I am human" checkbox. On solve, swap to the chat UI.
23
- * c. If both phases fail, fall through with no token; backend
24
- * enforcement returns 403 and the existing fallback card
25
- * renders. The widget mode must be `managed` on the CF side
26
- * for the visible escalation to work — see
27
- * cloudflare/turnstile/main.tf in syntro-infra.
14
+ * 2. Cloudflare Turnstile bot-check token. When `TURNSTILE_SITEKEY` is
15
+ * set, render a single `size: 'flexible'` widget with
16
+ * `appearance: 'interaction-only'` against a `managed`-mode sitekey:
17
+ * • Clean traffic: CF passive-grades silently, token resolves in
18
+ * ~100ms, no UI is ever shown.
19
+ * Flagged traffic (datacenter IP, automation, strict-privacy):
20
+ * CF renders an "I am human" checkbox inside the in-chat verify
21
+ * panel; on solve, swap to the chat UI.
22
+ * Total failure (CSP blocks CF, network error, user dismisses):
23
+ * fall through with no token; backend enforcement 403s and the
24
+ * existing fallback card renders.
25
+ * The widget mode MUST be `managed` on the CF side — see
26
+ * cloudflare/turnstile/main.tf in syntro-infra. (`mode = 'invisible'`
27
+ * rejects any `size` other than `invisible`, and `size: 'invisible'`
28
+ * against `mode = 'managed'` fails with a synchronous TurnstileError —
29
+ * don't mix.)
28
30
  * If sitekey is empty, this step is skipped entirely.
29
31
  *
30
32
  * 3. Fallback card. If the first agent run fails (Cloudflare Turnstile
@@ -47,18 +49,37 @@ export interface ChatAssistantLitProps {
47
49
  }
48
50
  export interface AcquireTurnstileOptions {
49
51
  /**
50
- * Widget size. `invisible` for the silent first attempt; `flexible`
51
- * (or `normal`) for the visible managed checkbox after silent failure.
52
+ * Widget size. `flexible` (default for managed-mode sitekeys) lets CF
53
+ * render a responsive checkbox when interaction is needed. `invisible`
54
+ * is only valid against an `invisible`-mode sitekey — pairing
55
+ * `invisible` with our `managed` sitekey produces a synchronous
56
+ * TurnstileError.
52
57
  */
53
- size: 'invisible' | 'flexible' | 'normal' | 'compact';
58
+ size?: 'flexible' | 'normal' | 'compact' | 'invisible';
54
59
  /**
55
- * Where to mount the widget. For `invisible` this is an off-screen
56
- * host; for the visible sizes it must be a container that's actually
57
- * in the layout so CF can render the challenge UI. When omitted, an
58
- * off-screen div is created and removed automatically (suitable for
59
- * `invisible` only visible sizes need a real host).
60
+ * `interaction-only` keeps the widget fully invisible during passive
61
+ * grading and only reveals UI if CF needs an interactive challenge.
62
+ * `always` (CF default) shows a small "verifying" indicator during
63
+ * grading. We default to `interaction-only` so clean traffic sees no
64
+ * flash before the chat appears.
65
+ */
66
+ appearance?: 'always' | 'execute' | 'interaction-only';
67
+ /**
68
+ * Where to mount the widget. For visible sizes (the default), this
69
+ * must be a host that's actually in the layout so CF has somewhere to
70
+ * draw the challenge UI if it needs to. When omitted, an off-screen
71
+ * div is created and removed automatically — only suitable for
72
+ * `size: 'invisible'` (which is only valid against an `invisible`-mode
73
+ * sitekey, which we don't ship).
60
74
  */
61
75
  host?: HTMLElement;
76
+ /**
77
+ * Fires when CF decides a challenge is needed and is about to reveal
78
+ * the checkbox. Caller uses it to surface accompanying copy
79
+ * ("Quick check before we start chatting") only when the challenge
80
+ * actually appears.
81
+ */
82
+ onBeforeInteractive?: () => void;
62
83
  /**
63
84
  * Cancels an in-flight acquisition. On abort, the promise resolves to
64
85
  * `null`, the CF widget is removed from the registry, and any owned
@@ -1 +1 @@
1
- {"version":3,"file":"ChatAssistantLit.d.ts","sourceRoot":"","sources":["../src/ChatAssistantLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAE,aAAa,EAAmB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAqGD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,GAAE,uBAA+C,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkFxB;AAqGD,eAAO,MAAM,yBAAyB;qBACnB,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAwPpE,CAAC"}
1
+ {"version":3,"file":"ChatAssistantLit.d.ts","sourceRoot":"","sources":["../src/ChatAssistantLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAE,aAAa,EAAmB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuGD,MAAM,WAAW,uBAAuB;IACtC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACvD;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,kBAAkB,CAAC;IACvD;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyFxB;AAqGD,eAAO,MAAM,yBAAyB;qBACnB,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAyPpE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ChatAssistantLitMountable,
3
3
  acquireTurnstileToken
4
- } from "./chunk-W457NMGD.js";
4
+ } from "./chunk-AMI4XIT5.js";
5
5
  import "./chunk-UVKRO5ER.js";
6
6
  export {
7
7
  ChatAssistantLitMountable,
@@ -10741,7 +10741,9 @@ function loadTurnstileScript() {
10741
10741
  document.head.appendChild(script);
10742
10742
  });
10743
10743
  }
10744
- async function acquireTurnstileToken(opts = { size: "invisible" }) {
10744
+ async function acquireTurnstileToken(opts = {}) {
10745
+ const size = opts.size ?? "flexible";
10746
+ const appearance = opts.appearance ?? "interaction-only";
10745
10747
  if (!TURNSTILE_SITEKEY) return null;
10746
10748
  if (typeof window === "undefined" || typeof document === "undefined") return null;
10747
10749
  let turnstile;
@@ -10792,11 +10794,13 @@ async function acquireTurnstileToken(opts = { size: "invisible" }) {
10792
10794
  try {
10793
10795
  widgetId = turnstile.render(host, {
10794
10796
  sitekey: TURNSTILE_SITEKEY,
10795
- size: opts.size,
10797
+ size,
10798
+ appearance,
10796
10799
  callback: (token) => settle(token),
10797
10800
  "error-callback": () => settle(null),
10798
10801
  "timeout-callback": () => settle(null),
10799
- "expired-callback": () => settle(null)
10802
+ "expired-callback": () => settle(null),
10803
+ "before-interactive-callback": () => opts.onBeforeInteractive?.()
10800
10804
  });
10801
10805
  } catch {
10802
10806
  settle(null);
@@ -10970,43 +10974,38 @@ var ChatAssistantLitMountable = {
10970
10974
  });
10971
10975
  container.appendChild(el);
10972
10976
  };
10973
- let visiblePanel = null;
10977
+ let verifyPanel = null;
10974
10978
  const acquireAbort = new AbortController();
10975
- const acquireWithEscalation = async () => {
10976
- const silent = await acquireTurnstileToken({
10977
- size: "invisible",
10978
- signal: acquireAbort.signal
10979
- });
10980
- if (isUnmounted) return null;
10981
- if (silent !== null) {
10982
- botCheckOutcome = "invisible_succeeded";
10983
- return silent;
10984
- }
10985
- visiblePanel = renderVerifyPanel(container);
10986
- const widgetHost = visiblePanel.querySelector(
10979
+ const acquireWithManagedChallenge = async () => {
10980
+ verifyPanel = renderVerifyPanel(container);
10981
+ const widgetHost = verifyPanel.querySelector(
10987
10982
  '[data-adaptive-chatbot-turnstile-host="true"]'
10988
10983
  );
10989
10984
  if (!widgetHost) return null;
10990
- const interactive = await acquireTurnstileToken({
10991
- size: "flexible",
10985
+ let isInteractive = false;
10986
+ const token2 = await acquireTurnstileToken({
10992
10987
  host: widgetHost,
10993
- signal: acquireAbort.signal
10988
+ signal: acquireAbort.signal,
10989
+ onBeforeInteractive: () => {
10990
+ isInteractive = true;
10991
+ revealVerifyPanelHeading(verifyPanel);
10992
+ }
10994
10993
  });
10995
10994
  if (isUnmounted) return null;
10996
- visiblePanel.remove();
10997
- visiblePanel = null;
10998
- if (interactive !== null) {
10999
- botCheckOutcome = "visible_succeeded";
11000
- return interactive;
10995
+ verifyPanel.remove();
10996
+ verifyPanel = null;
10997
+ if (token2 !== null) {
10998
+ botCheckOutcome = isInteractive ? "interactive_succeeded" : "silent_succeeded";
10999
+ return token2;
11001
11000
  }
11002
- botCheckOutcome = "visible_failed";
11001
+ botCheckOutcome = "acquisition_failed";
11003
11002
  console.warn(
11004
- "[adaptive-chatbot] turnstile token acquisition failed (visible challenge also rejected) \u2014 connecting without a bot-check token; backend may 403"
11003
+ "[adaptive-chatbot] turnstile token acquisition failed \u2014 connecting without a bot-check token; backend may 403"
11005
11004
  );
11006
11005
  return null;
11007
11006
  };
11008
11007
  if (TURNSTILE_SITEKEY) {
11009
- void acquireWithEscalation().then((cft) => {
11008
+ void acquireWithManagedChallenge().then((cft) => {
11010
11009
  if (isUnmounted) return;
11011
11010
  hadTurnstileToken = cft !== null;
11012
11011
  setupTransport(cft);
@@ -11022,9 +11021,9 @@ var ChatAssistantLitMountable = {
11022
11021
  unsubscribe();
11023
11022
  unsubscribe = null;
11024
11023
  }
11025
- if (visiblePanel) {
11026
- visiblePanel.remove();
11027
- visiblePanel = null;
11024
+ if (verifyPanel) {
11025
+ verifyPanel.remove();
11026
+ verifyPanel = null;
11028
11027
  }
11029
11028
  if (!fallbackRendered) {
11030
11029
  transport?.disconnect();
@@ -11051,7 +11050,8 @@ function renderVerifyPanel(container) {
11051
11050
  "text-align:center"
11052
11051
  ].join(";");
11053
11052
  const heading = document.createElement("div");
11054
- heading.style.cssText = "font-size:14px;font-weight:500;line-height:1.4;";
11053
+ heading.setAttribute("data-adaptive-chatbot-turnstile-heading", "true");
11054
+ heading.style.cssText = "font-size:14px;font-weight:500;line-height:1.4;display:none;";
11055
11055
  heading.textContent = "Quick check before we start chatting";
11056
11056
  const widgetHost = document.createElement("div");
11057
11057
  widgetHost.setAttribute("data-adaptive-chatbot-turnstile-host", "true");
@@ -11060,6 +11060,13 @@ function renderVerifyPanel(container) {
11060
11060
  container.appendChild(panel);
11061
11061
  return panel;
11062
11062
  }
11063
+ function revealVerifyPanelHeading(panel) {
11064
+ if (!panel) return;
11065
+ const heading = panel.querySelector(
11066
+ '[data-adaptive-chatbot-turnstile-heading="true"]'
11067
+ );
11068
+ if (heading) heading.style.display = "";
11069
+ }
11063
11070
 
11064
11071
  export {
11065
11072
  acquireTurnstileToken,
@@ -11084,4 +11091,4 @@ fast-json-patch/module/duplex.mjs:
11084
11091
  * MIT license
11085
11092
  *)
11086
11093
  */
11087
- //# sourceMappingURL=chunk-W457NMGD.js.map
11094
+ //# sourceMappingURL=chunk-AMI4XIT5.js.map