@syntrologie/adapt-chatbot 2.8.0-canary.244 → 2.8.0-canary.245
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.
- package/dist/ChatAssistantLit.d.ts +43 -3
- package/dist/ChatAssistantLit.d.ts.map +1 -1
- package/dist/ChatAssistantLit.js +5 -3
- package/dist/{chunk-FM7MU2YX.js → chunk-DUQTO5RE.js} +427 -23
- package/dist/{chunk-FM7MU2YX.js.map → chunk-DUQTO5RE.js.map} +4 -4
- package/dist/runtime.js +1 -1
- package/dist/schema.d.ts +92 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +42 -3
- package/dist/schema.js.map +2 -2
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -4,9 +4,34 @@
|
|
|
4
4
|
* Thin wrapper that mounts <syntro-chat> from @syntrologie/chat with an
|
|
5
5
|
* SsePostTransport pointed at the adaptive SSE backend.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Four pieces this mountable owns:
|
|
8
|
+
*
|
|
9
|
+
* 1. SDK token plumbing. The chat backend (`/api/adaptive/*`) is identified
|
|
10
|
+
* by the workspace's `syn_*` SDK token. The token lives on
|
|
11
|
+
* `window.__SYNTRO_CONFIG__.token` (set by runtime-config.js) — we read
|
|
12
|
+
* it at mount time and forward it on both transports:
|
|
13
|
+
* • SSE GET — append `?syn=${token}` to the URL. Browser EventSource
|
|
14
|
+
* cannot set custom headers; the URL is the only channel.
|
|
15
|
+
* • POST `/action` — `Authorization: Bearer ${token}` header.
|
|
16
|
+
*
|
|
17
|
+
* 2. Cloudflare Turnstile bot-check token. When `TURNSTILE_SITEKEY` is set
|
|
18
|
+
* (hardcoded at the top of this file — operator updates after running
|
|
19
|
+
* terraform apply on cloudflare/turnstile in the infra repo), the
|
|
20
|
+
* widget dynamically loads Cloudflare's Turnstile JS, renders an
|
|
21
|
+
* invisible widget, and acquires a token before opening the SSE
|
|
22
|
+
* connection. The token is forwarded on both transports the same way
|
|
23
|
+
* the SDK token is — `?cft=` on SSE GET, `CF-Turnstile-Token` on POST.
|
|
24
|
+
* If sitekey is empty (default), this step is skipped entirely.
|
|
25
|
+
*
|
|
26
|
+
* 3. Fallback card. If the SSE connection fails before any successful event
|
|
27
|
+
* (Cloudflare Turnstile bot-check failure, CORS / network error, or a
|
|
28
|
+
* server-side rejection), swap the chat UI for a per-customer fallback
|
|
29
|
+
* card built from `props.fallback`. The end-user is redirected to the
|
|
30
|
+
* customer's normal support path instead of seeing a raw error.
|
|
31
|
+
*
|
|
32
|
+
* 4. A2UI passthrough. SSE-borne A2UI events (createSurface, updateDataModel,
|
|
33
|
+
* …) get forwarded to `runtime.actions.applyBatch` so tiles can render
|
|
34
|
+
* server-driven UI inside the chat surface.
|
|
10
35
|
*/
|
|
11
36
|
import '@syntrologie/chat';
|
|
12
37
|
import type { ChatbotConfig, ChatbotWidgetRuntime } from './types.js';
|
|
@@ -15,6 +40,21 @@ export interface ChatAssistantLitProps {
|
|
|
15
40
|
runtime: ChatbotWidgetRuntime;
|
|
16
41
|
tileId?: string;
|
|
17
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Acquire a Cloudflare Turnstile token. Returns null when:
|
|
45
|
+
* • TURNSTILE_SITEKEY is empty (Turnstile disabled at build time)
|
|
46
|
+
* • the script fails to load (customer CSP blocks Cloudflare, etc.)
|
|
47
|
+
* • the render callback doesn't fire within TURNSTILE_ACQUIRE_TIMEOUT_MS
|
|
48
|
+
* • Cloudflare returns an error-callback (e.g., rate-limited, bad sitekey)
|
|
49
|
+
*
|
|
50
|
+
* When null is returned, the widget proceeds WITHOUT a Turnstile token.
|
|
51
|
+
* If backend enforcement is on, the request 403s and the fallback card
|
|
52
|
+
* renders via the existing failure-detection path. If enforcement is off,
|
|
53
|
+
* the request succeeds.
|
|
54
|
+
*
|
|
55
|
+
* Exported for tests; the production code path goes through `mount()`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function acquireTurnstileToken(): Promise<string | null>;
|
|
18
58
|
export declare const ChatAssistantLitMountable: {
|
|
19
59
|
mount(container: HTMLElement, mountConfig?: Record<string, unknown>): () => void;
|
|
20
60
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatAssistantLit.d.ts","sourceRoot":"","sources":["../src/ChatAssistantLit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ChatAssistantLit.d.ts","sourceRoot":"","sources":["../src/ChatAssistantLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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;AAyHD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkDpE;AAiGD,eAAO,MAAM,yBAAyB;qBACnB,WAAW,gBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAwOpE,CAAC"}
|
package/dist/ChatAssistantLit.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ChatAssistantLitMountable
|
|
3
|
-
|
|
2
|
+
ChatAssistantLitMountable,
|
|
3
|
+
acquireTurnstileToken
|
|
4
|
+
} from "./chunk-DUQTO5RE.js";
|
|
4
5
|
import "./chunk-V6TY7KAL.js";
|
|
5
6
|
export {
|
|
6
|
-
ChatAssistantLitMountable
|
|
7
|
+
ChatAssistantLitMountable,
|
|
8
|
+
acquireTurnstileToken
|
|
7
9
|
};
|
|
8
10
|
//# sourceMappingURL=ChatAssistantLit.js.map
|
|
@@ -3218,8 +3218,12 @@ var SsePostTransport = class {
|
|
|
3218
3218
|
return this._connected;
|
|
3219
3219
|
}
|
|
3220
3220
|
connect(sessionId) {
|
|
3221
|
-
const
|
|
3222
|
-
|
|
3221
|
+
const baseOrigin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
|
|
3222
|
+
const url = new URL(this._sseUrl, baseOrigin);
|
|
3223
|
+
if (sessionId) {
|
|
3224
|
+
url.searchParams.set("sessionId", sessionId);
|
|
3225
|
+
}
|
|
3226
|
+
this._eventSource = new EventSource(url.toString(), { withCredentials: true });
|
|
3223
3227
|
this._eventSource.onopen = () => {
|
|
3224
3228
|
this._connected = true;
|
|
3225
3229
|
};
|
|
@@ -3277,12 +3281,330 @@ var SsePostTransport = class {
|
|
|
3277
3281
|
}
|
|
3278
3282
|
};
|
|
3279
3283
|
|
|
3284
|
+
// ../../sdk-contracts/dist/mount-plumbing.js
|
|
3285
|
+
var MOUNT_PLUMBING_KEYS = ["instanceId", "runtime", "tileId"];
|
|
3286
|
+
function stripMountPlumbing(config) {
|
|
3287
|
+
if (!config || typeof config !== "object") {
|
|
3288
|
+
return {};
|
|
3289
|
+
}
|
|
3290
|
+
const out = { ...config };
|
|
3291
|
+
for (const key of MOUNT_PLUMBING_KEYS) {
|
|
3292
|
+
delete out[key];
|
|
3293
|
+
}
|
|
3294
|
+
return out;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
// ../../sdk-contracts/dist/schemas.js
|
|
3298
|
+
import { z as z2 } from "zod";
|
|
3299
|
+
var AnchorIdZ = z2.object({
|
|
3300
|
+
selector: z2.string(),
|
|
3301
|
+
route: z2.union([z2.string(), z2.array(z2.string())])
|
|
3302
|
+
}).strict();
|
|
3303
|
+
var AuthoringFieldsZ = {
|
|
3304
|
+
title: z2.string().max(200).optional().describe("Authoring-only: short label shown on the action plan dashboard. Stripped before serving to the runtime SDK."),
|
|
3305
|
+
description: z2.string().max(1e3).optional().describe("Authoring-only: one-sentence explanation of what this action does and why. Stripped before serving to the runtime SDK."),
|
|
3306
|
+
validation: z2.array(z2.string().max(500)).max(10).optional().describe("Authoring-only: ordered steps a reviewer can follow to trigger this action and visually confirm it works. Each entry is one step. Stripped before serving to the runtime SDK.")
|
|
3307
|
+
};
|
|
3308
|
+
var COUNTABLE_EVENTS = [
|
|
3309
|
+
// User interactions (from PostHog autocapture normalization)
|
|
3310
|
+
"ui.click",
|
|
3311
|
+
"ui.scroll",
|
|
3312
|
+
"ui.input",
|
|
3313
|
+
"ui.change",
|
|
3314
|
+
"ui.submit",
|
|
3315
|
+
// Behavioral detectors (from event-processor)
|
|
3316
|
+
"ui.hover",
|
|
3317
|
+
"ui.idle",
|
|
3318
|
+
"ui.scroll_thrash",
|
|
3319
|
+
"ui.focus_bounce",
|
|
3320
|
+
// Navigation
|
|
3321
|
+
"nav.page_view",
|
|
3322
|
+
"nav.page_leave",
|
|
3323
|
+
// Derived behavioral signals
|
|
3324
|
+
"behavior.rage_click",
|
|
3325
|
+
"behavior.hesitation",
|
|
3326
|
+
"behavior.confusion"
|
|
3327
|
+
];
|
|
3328
|
+
var CountableEventZ = z2.enum(COUNTABLE_EVENTS).describe("Event name to count. ui.* = user interactions and behavioral detectors, nav.* = page navigation, behavior.* = derived behavioral signals.");
|
|
3329
|
+
var SESSION_METRIC_KEYS = ["time_on_page", "page_views", "scroll_depth"];
|
|
3330
|
+
var SessionMetricKeyZ = z2.enum(SESSION_METRIC_KEYS).describe("Session metric key. time_on_page = seconds on current page, page_views = pages visited this session, scroll_depth = 0-100 percentage.");
|
|
3331
|
+
var PageUrlConditionZ = z2.object({
|
|
3332
|
+
type: z2.literal("page_url"),
|
|
3333
|
+
url: z2.string().describe('URL path to match (e.g. "/pricing", "/dashboard")')
|
|
3334
|
+
}).describe('Fires when the current page URL matches. Use for page-specific actions. Example: {"type": "page_url", "url": "/pricing"}');
|
|
3335
|
+
var RouteConditionZ = z2.object({
|
|
3336
|
+
type: z2.literal("route"),
|
|
3337
|
+
routeId: z2.string().describe("Named route ID from the route filter")
|
|
3338
|
+
}).describe("Fires when the current route matches a named route ID.");
|
|
3339
|
+
var AnchorVisibleConditionZ = z2.object({
|
|
3340
|
+
type: z2.literal("anchor_visible"),
|
|
3341
|
+
anchorId: z2.string().describe("CSS selector of the anchor element"),
|
|
3342
|
+
state: z2.enum(["visible", "present", "absent"]).describe('"visible" = in viewport, "present" = in DOM, "absent" = not in DOM')
|
|
3343
|
+
}).describe(`Fires based on a DOM element's visibility state. Example: {"type": "anchor_visible", "anchorId": "#cta-button", "state": "visible"}`);
|
|
3344
|
+
var EventOccurredConditionZ = z2.object({
|
|
3345
|
+
type: z2.literal("event_occurred"),
|
|
3346
|
+
eventName: z2.string().describe('Event name (e.g. "ui.click", "$pageview")'),
|
|
3347
|
+
withinMs: z2.number().optional().describe("Time window in ms. Omit = any time this session.")
|
|
3348
|
+
}).describe('Fires when a specific event has occurred during this session. Example: {"type": "event_occurred", "eventName": "ui.click", "withinMs": 5000}');
|
|
3349
|
+
var StateEqualsConditionZ = z2.object({
|
|
3350
|
+
type: z2.literal("state_equals"),
|
|
3351
|
+
key: z2.string().describe("Key in the SDK persistent state store (localStorage). Only valid for keys the host app explicitly sets via syntro.state.set()."),
|
|
3352
|
+
value: z2.unknown().describe("Expected value to match against")
|
|
3353
|
+
}).describe("Checks the SDK persistent state store (localStorage). ONLY for host-app state set via syntro.state.set() \u2014 NOT for user attributes like region, device, or UTM params (those are handled by segment targeting). Do NOT use this for targeting. If you do not know the valid state keys, do not use this condition type.");
|
|
3354
|
+
var ViewportConditionZ = z2.object({
|
|
3355
|
+
type: z2.literal("viewport"),
|
|
3356
|
+
minWidth: z2.number().optional().describe("Minimum viewport width in pixels"),
|
|
3357
|
+
maxWidth: z2.number().optional().describe("Maximum viewport width in pixels"),
|
|
3358
|
+
minHeight: z2.number().optional().describe("Minimum viewport height in pixels"),
|
|
3359
|
+
maxHeight: z2.number().optional().describe("Maximum viewport height in pixels")
|
|
3360
|
+
}).describe('Fires based on viewport (screen) size. Use for responsive behavior. Example: {"type": "viewport", "minWidth": 768} \u2014 fires on tablet and larger.');
|
|
3361
|
+
var SessionMetricConditionZ = z2.object({
|
|
3362
|
+
type: z2.literal("session_metric"),
|
|
3363
|
+
key: SessionMetricKeyZ,
|
|
3364
|
+
operator: z2.enum(["gte", "lte", "eq", "gt", "lt"]),
|
|
3365
|
+
threshold: z2.number().describe("Numeric threshold to compare against")
|
|
3366
|
+
}).describe('Fires when a session metric crosses a threshold. Valid keys: "time_on_page" (seconds), "page_views" (count), "scroll_depth" (0-100). Example: {"type": "session_metric", "key": "time_on_page", "operator": "gte", "threshold": 30}');
|
|
3367
|
+
var DismissedConditionZ = z2.object({
|
|
3368
|
+
type: z2.literal("dismissed"),
|
|
3369
|
+
key: z2.string().describe("Dismissal key (usually a tile or action ID)"),
|
|
3370
|
+
inverted: z2.boolean().optional().describe("When true, fires if NOT dismissed (default behavior)")
|
|
3371
|
+
}).describe("Checks if an item has been dismissed by the user. Use with inverted: true to show only if not dismissed.");
|
|
3372
|
+
var CooldownActiveConditionZ = z2.object({
|
|
3373
|
+
type: z2.literal("cooldown_active"),
|
|
3374
|
+
key: z2.string().describe("Cooldown key"),
|
|
3375
|
+
inverted: z2.boolean().optional().describe("When true, fires if cooldown is NOT active")
|
|
3376
|
+
}).describe("Checks if a cooldown timer is currently active. Use to prevent showing the same intervention too frequently.");
|
|
3377
|
+
var FrequencyLimitConditionZ = z2.object({
|
|
3378
|
+
type: z2.literal("frequency_limit"),
|
|
3379
|
+
key: z2.string().describe("Frequency counter key"),
|
|
3380
|
+
limit: z2.number().describe("Maximum allowed count"),
|
|
3381
|
+
inverted: z2.boolean().optional().describe("When true, fires if limit NOT reached")
|
|
3382
|
+
}).describe("Checks if a frequency limit has been reached. Use to cap how many times an action fires per session.");
|
|
3383
|
+
var MatchOpZ = z2.object({
|
|
3384
|
+
equals: z2.union([z2.string(), z2.number(), z2.boolean()]).optional(),
|
|
3385
|
+
contains: z2.string().optional()
|
|
3386
|
+
}).describe("Match operator for counter filters. Exactly one of equals or contains must be specified.");
|
|
3387
|
+
var CounterDefZ = z2.object({
|
|
3388
|
+
events: z2.array(CountableEventZ).min(1).describe("Event names to count. Use values from the countable events enum."),
|
|
3389
|
+
match: z2.record(z2.string(), MatchOpZ).optional().describe("Property filters. Keys are event prop names or element-chain fields (tag_name, $el_text, attr__*). All entries AND together.")
|
|
3390
|
+
}).describe("Defines what events to count. Registered as an accumulator predicate at config-load time.");
|
|
3391
|
+
var EventCountConditionZ = z2.object({
|
|
3392
|
+
type: z2.literal("event_count"),
|
|
3393
|
+
key: z2.string().describe("Unique key for this counter (used for accumulator registration)"),
|
|
3394
|
+
operator: z2.enum(["gte", "lte", "eq", "gt", "lt"]),
|
|
3395
|
+
count: z2.number().int().min(0).describe("Target count threshold"),
|
|
3396
|
+
withinMs: z2.number().positive().optional().describe("Time window in ms. Omit = count across entire session."),
|
|
3397
|
+
counter: CounterDefZ.optional().describe("Inline counter definition. Defines what events to count.")
|
|
3398
|
+
}).describe('Fires when accumulated event count crosses a threshold. Most powerful trigger type. Example: {"type": "event_count", "key": "pricing-clicks", "operator": "gte", "count": 3, "counter": {"events": ["ui.click"], "match": {"attr__data-cta": {"contains": "pricing"}}}}');
|
|
3399
|
+
var ConditionZ = z2.discriminatedUnion("type", [
|
|
3400
|
+
PageUrlConditionZ,
|
|
3401
|
+
RouteConditionZ,
|
|
3402
|
+
AnchorVisibleConditionZ,
|
|
3403
|
+
EventOccurredConditionZ,
|
|
3404
|
+
StateEqualsConditionZ,
|
|
3405
|
+
ViewportConditionZ,
|
|
3406
|
+
SessionMetricConditionZ,
|
|
3407
|
+
DismissedConditionZ,
|
|
3408
|
+
CooldownActiveConditionZ,
|
|
3409
|
+
FrequencyLimitConditionZ,
|
|
3410
|
+
EventCountConditionZ
|
|
3411
|
+
]);
|
|
3412
|
+
var RuleZ = z2.object({
|
|
3413
|
+
conditions: z2.array(ConditionZ).describe("Array of conditions \u2014 ALL must match (AND logic) for this rule to fire."),
|
|
3414
|
+
value: z2.unknown().describe("Value returned when all conditions match. For triggerWhen: true = fire the action.")
|
|
3415
|
+
}).describe("A single rule. ALL conditions must match (AND logic). Rules in a strategy are evaluated top-to-bottom \u2014 first rule where all conditions match wins and returns its value.");
|
|
3416
|
+
var RuleStrategyZ = z2.object({
|
|
3417
|
+
type: z2.literal("rules"),
|
|
3418
|
+
rules: z2.array(RuleZ).describe("Ordered list of rules. Evaluated top-to-bottom \u2014 first match wins."),
|
|
3419
|
+
default: z2.unknown().describe("Fallback value when no rule matches. For triggerWhen: false = do not fire by default.")
|
|
3420
|
+
}).describe("Rule-based strategy. Evaluates rules top-to-bottom. First rule where ALL conditions match returns its value. If no rule matches, returns default. For triggerWhen: set value=true on matching rules, default=false.");
|
|
3421
|
+
var ScoreStrategyZ = z2.object({
|
|
3422
|
+
type: z2.literal("score"),
|
|
3423
|
+
field: z2.string(),
|
|
3424
|
+
threshold: z2.number(),
|
|
3425
|
+
above: z2.unknown(),
|
|
3426
|
+
below: z2.unknown()
|
|
3427
|
+
}).describe("Score-based strategy. Compares a field value against a threshold.");
|
|
3428
|
+
var ModelStrategyZ = z2.object({
|
|
3429
|
+
type: z2.literal("model"),
|
|
3430
|
+
modelId: z2.string(),
|
|
3431
|
+
inputs: z2.array(z2.string()),
|
|
3432
|
+
outputMapping: z2.record(z2.string(), z2.unknown()),
|
|
3433
|
+
default: z2.unknown()
|
|
3434
|
+
}).describe("ML model strategy. Sends inputs to a model and maps outputs.");
|
|
3435
|
+
var ExternalStrategyZ = z2.object({
|
|
3436
|
+
type: z2.literal("external"),
|
|
3437
|
+
endpoint: z2.string(),
|
|
3438
|
+
method: z2.enum(["GET", "POST"]).optional(),
|
|
3439
|
+
default: z2.unknown(),
|
|
3440
|
+
timeoutMs: z2.number().optional()
|
|
3441
|
+
}).describe("External API strategy. Calls an endpoint to determine the value.");
|
|
3442
|
+
var DecisionStrategyZ = z2.discriminatedUnion("type", [
|
|
3443
|
+
RuleStrategyZ,
|
|
3444
|
+
ScoreStrategyZ,
|
|
3445
|
+
ModelStrategyZ,
|
|
3446
|
+
ExternalStrategyZ
|
|
3447
|
+
]);
|
|
3448
|
+
var TriggerWhenZ = DecisionStrategyZ.nullable().optional();
|
|
3449
|
+
var EventScopeZ = z2.object({
|
|
3450
|
+
events: z2.array(z2.string()),
|
|
3451
|
+
urlContains: z2.string().optional(),
|
|
3452
|
+
props: z2.record(z2.union([z2.string(), z2.number(), z2.boolean()])).optional()
|
|
3453
|
+
});
|
|
3454
|
+
var NotifyZ = z2.object({
|
|
3455
|
+
title: z2.string().optional(),
|
|
3456
|
+
body: z2.string().optional(),
|
|
3457
|
+
icon: z2.string().optional()
|
|
3458
|
+
}).nullable().optional();
|
|
3459
|
+
|
|
3280
3460
|
// src/ChatAssistantLit.ts
|
|
3461
|
+
var ERROR_DEBOUNCE_MS = 1500;
|
|
3462
|
+
var FAILURE_DETECTION_TIMEOUT_MS = 6e3;
|
|
3463
|
+
var TURNSTILE_SITEKEY = "0x4AAAAAADSzPbGWxeguF4YJ";
|
|
3464
|
+
var TURNSTILE_ACQUIRE_TIMEOUT_MS = 5e3;
|
|
3465
|
+
var TURNSTILE_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
3466
|
+
function loadTurnstileScript() {
|
|
3467
|
+
if (typeof window === "undefined") {
|
|
3468
|
+
return Promise.reject(new Error("no window"));
|
|
3469
|
+
}
|
|
3470
|
+
const w2 = window;
|
|
3471
|
+
if (w2.turnstile) return Promise.resolve(w2.turnstile);
|
|
3472
|
+
return new Promise((resolve, reject) => {
|
|
3473
|
+
const existing = document.querySelector(
|
|
3474
|
+
'script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]'
|
|
3475
|
+
);
|
|
3476
|
+
const onLoad = () => {
|
|
3477
|
+
const t = window.turnstile;
|
|
3478
|
+
if (t) resolve(t);
|
|
3479
|
+
else reject(new Error("turnstile global not present after load"));
|
|
3480
|
+
};
|
|
3481
|
+
const onError = () => reject(new Error("turnstile script failed to load"));
|
|
3482
|
+
if (existing) {
|
|
3483
|
+
existing.addEventListener("load", onLoad);
|
|
3484
|
+
existing.addEventListener("error", onError);
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
const script = document.createElement("script");
|
|
3488
|
+
script.src = TURNSTILE_SCRIPT_URL;
|
|
3489
|
+
script.async = true;
|
|
3490
|
+
script.defer = true;
|
|
3491
|
+
script.addEventListener("load", onLoad);
|
|
3492
|
+
script.addEventListener("error", onError);
|
|
3493
|
+
document.head.appendChild(script);
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
async function acquireTurnstileToken() {
|
|
3497
|
+
if (!TURNSTILE_SITEKEY) return null;
|
|
3498
|
+
if (typeof window === "undefined" || typeof document === "undefined") return null;
|
|
3499
|
+
let turnstile;
|
|
3500
|
+
try {
|
|
3501
|
+
turnstile = await Promise.race([
|
|
3502
|
+
loadTurnstileScript(),
|
|
3503
|
+
new Promise(
|
|
3504
|
+
(_2, reject) => setTimeout(() => reject(new Error("turnstile load timeout")), TURNSTILE_ACQUIRE_TIMEOUT_MS)
|
|
3505
|
+
)
|
|
3506
|
+
]);
|
|
3507
|
+
} catch {
|
|
3508
|
+
return null;
|
|
3509
|
+
}
|
|
3510
|
+
return new Promise((resolve) => {
|
|
3511
|
+
const host = document.createElement("div");
|
|
3512
|
+
host.style.cssText = "position:absolute;top:-9999px;left:-9999px;width:0;height:0;overflow:hidden;";
|
|
3513
|
+
host.setAttribute("data-adaptive-chatbot-turnstile-host", "true");
|
|
3514
|
+
document.body.appendChild(host);
|
|
3515
|
+
let settled = false;
|
|
3516
|
+
const settle = (value) => {
|
|
3517
|
+
if (settled) return;
|
|
3518
|
+
settled = true;
|
|
3519
|
+
clearTimeout(timer);
|
|
3520
|
+
setTimeout(() => host.remove(), 0);
|
|
3521
|
+
resolve(value);
|
|
3522
|
+
};
|
|
3523
|
+
const timer = setTimeout(() => settle(null), TURNSTILE_ACQUIRE_TIMEOUT_MS);
|
|
3524
|
+
try {
|
|
3525
|
+
turnstile.render(host, {
|
|
3526
|
+
sitekey: TURNSTILE_SITEKEY,
|
|
3527
|
+
size: "invisible",
|
|
3528
|
+
callback: (token) => settle(token),
|
|
3529
|
+
"error-callback": () => settle(null),
|
|
3530
|
+
"timeout-callback": () => settle(null),
|
|
3531
|
+
"expired-callback": () => settle(null)
|
|
3532
|
+
});
|
|
3533
|
+
} catch {
|
|
3534
|
+
settle(null);
|
|
3535
|
+
}
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
function readSyntroToken() {
|
|
3539
|
+
if (typeof window === "undefined") return void 0;
|
|
3540
|
+
const cfg = window.__SYNTRO_CONFIG__;
|
|
3541
|
+
const token = cfg?.token;
|
|
3542
|
+
return typeof token === "string" && token.startsWith("syn_") ? token : void 0;
|
|
3543
|
+
}
|
|
3544
|
+
var FALLBACK_DEFAULTS = {
|
|
3545
|
+
title: "Live chat is unavailable right now",
|
|
3546
|
+
message: "We couldn't verify your browser. Reach our team another way and we'll get back to you.",
|
|
3547
|
+
ctaLabel: "Contact support"
|
|
3548
|
+
};
|
|
3549
|
+
function renderFallbackHtml(fallback) {
|
|
3550
|
+
const title = fallback?.title ?? FALLBACK_DEFAULTS.title;
|
|
3551
|
+
const message = fallback?.message ?? FALLBACK_DEFAULTS.message;
|
|
3552
|
+
const ctaLabel = fallback?.ctaLabel ?? FALLBACK_DEFAULTS.ctaLabel;
|
|
3553
|
+
const ctaHref = fallback?.ctaHref;
|
|
3554
|
+
const wrapperStyle = [
|
|
3555
|
+
"display:flex",
|
|
3556
|
+
"flex-direction:column",
|
|
3557
|
+
"gap:12px",
|
|
3558
|
+
"padding:20px",
|
|
3559
|
+
"height:100%",
|
|
3560
|
+
"background:var(--sc-content-background,'transparent')",
|
|
3561
|
+
"color:var(--sc-content-text-color,'#1a1a1a')",
|
|
3562
|
+
"font-family:var(--sc-font-family,'system-ui')"
|
|
3563
|
+
].join(";");
|
|
3564
|
+
const titleStyle = "font-size:15px;font-weight:600;line-height:1.3;";
|
|
3565
|
+
const messageStyle = "font-size:14px;line-height:1.5;color:var(--sc-content-text-secondary-color,#6b7280);";
|
|
3566
|
+
const ctaStyle = [
|
|
3567
|
+
"display:inline-block",
|
|
3568
|
+
"margin-top:4px",
|
|
3569
|
+
"padding:10px 16px",
|
|
3570
|
+
"background:var(--sc-color-primary,'#111')",
|
|
3571
|
+
"color:var(--sc-color-primary-foreground,'#fff')",
|
|
3572
|
+
"text-decoration:none",
|
|
3573
|
+
"border-radius:8px",
|
|
3574
|
+
"font-size:14px",
|
|
3575
|
+
"font-weight:500",
|
|
3576
|
+
"align-self:flex-start"
|
|
3577
|
+
].join(";");
|
|
3578
|
+
const escapeHtml = (s) => s.replace(/[&<>"']/g, (ch) => {
|
|
3579
|
+
switch (ch) {
|
|
3580
|
+
case "&":
|
|
3581
|
+
return "&";
|
|
3582
|
+
case "<":
|
|
3583
|
+
return "<";
|
|
3584
|
+
case ">":
|
|
3585
|
+
return ">";
|
|
3586
|
+
case '"':
|
|
3587
|
+
return """;
|
|
3588
|
+
default:
|
|
3589
|
+
return "'";
|
|
3590
|
+
}
|
|
3591
|
+
});
|
|
3592
|
+
const ctaHtml = ctaHref && /^https?:\/\//.test(ctaHref) ? `<a href="${escapeHtml(ctaHref)}" target="_blank" rel="noopener noreferrer" style="${ctaStyle}">${escapeHtml(ctaLabel)}</a>` : "";
|
|
3593
|
+
return `<div style="${wrapperStyle}" data-adaptive-chatbot-fallback="true"><div style="${titleStyle}">${escapeHtml(title)}</div><div style="${messageStyle}">${escapeHtml(message)}</div>${ctaHtml}</div>`;
|
|
3594
|
+
}
|
|
3281
3595
|
var ChatAssistantLitMountable = {
|
|
3282
3596
|
mount(container, mountConfig) {
|
|
3283
|
-
const incoming = mountConfig ??
|
|
3284
|
-
const
|
|
3285
|
-
const
|
|
3597
|
+
const incoming = mountConfig ?? null;
|
|
3598
|
+
const runtime = incoming?.runtime;
|
|
3599
|
+
const instanceId = incoming?.instanceId;
|
|
3600
|
+
const tileId = incoming?.tileId;
|
|
3601
|
+
const nestedConfig = incoming?.config;
|
|
3602
|
+
const flat = stripMountPlumbing(incoming);
|
|
3603
|
+
if ("config" in flat) {
|
|
3604
|
+
delete flat.config;
|
|
3605
|
+
}
|
|
3606
|
+
const flatLooksLikeChatbotConfig = typeof flat.backendUrl === "string";
|
|
3607
|
+
const chatbotConfig = nestedConfig ?? (flatLooksLikeChatbotConfig ? flat : void 0);
|
|
3286
3608
|
const resolvedTileId = tileId ?? instanceId ?? "chatbot-widget";
|
|
3287
3609
|
if (!chatbotConfig?.backendUrl || !runtime) {
|
|
3288
3610
|
container.innerHTML = '<div style="padding:16px;color:var(--sc-content-text-secondary-color,#87919f)">Chat widget requires config and runtime.</div>';
|
|
@@ -3291,35 +3613,117 @@ var ChatAssistantLitMountable = {
|
|
|
3291
3613
|
};
|
|
3292
3614
|
}
|
|
3293
3615
|
const baseUrl = chatbotConfig.backendUrl.replace(/\/$/, "");
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3616
|
+
const token = readSyntroToken();
|
|
3617
|
+
const postUrl = `${baseUrl}/api/adaptive/action`;
|
|
3618
|
+
const buildSseUrl = (cft) => {
|
|
3619
|
+
const parts = [];
|
|
3620
|
+
if (token) parts.push(`syn=${encodeURIComponent(token)}`);
|
|
3621
|
+
if (cft) parts.push(`cft=${encodeURIComponent(cft)}`);
|
|
3622
|
+
return parts.length ? `${baseUrl}/api/adaptive/session?${parts.join("&")}` : `${baseUrl}/api/adaptive/session`;
|
|
3623
|
+
};
|
|
3624
|
+
const buildHeaders = (cft) => {
|
|
3625
|
+
const h = {};
|
|
3626
|
+
if (token) h.Authorization = `Bearer ${token}`;
|
|
3627
|
+
if (cft) h["CF-Turnstile-Token"] = cft;
|
|
3628
|
+
return h;
|
|
3629
|
+
};
|
|
3307
3630
|
const el = document.createElement("syntro-chat");
|
|
3308
|
-
el.transport = transport;
|
|
3309
3631
|
el.greeting = chatbotConfig.greeting;
|
|
3310
3632
|
if (chatbotConfig.suggestions) {
|
|
3311
3633
|
el.suggestions = chatbotConfig.suggestions;
|
|
3312
3634
|
}
|
|
3313
3635
|
el.style.cssText = "display:flex;flex-direction:column;height:100%;width:100%;";
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3636
|
+
let transport = null;
|
|
3637
|
+
let hasSucceeded = false;
|
|
3638
|
+
let isUnmounted = false;
|
|
3639
|
+
let unsubscribe = null;
|
|
3640
|
+
let failureTimer = null;
|
|
3641
|
+
let errorDebounceTimer = null;
|
|
3642
|
+
let fallbackRendered = false;
|
|
3643
|
+
const clearTimers = () => {
|
|
3644
|
+
if (failureTimer) {
|
|
3645
|
+
clearTimeout(failureTimer);
|
|
3646
|
+
failureTimer = null;
|
|
3647
|
+
}
|
|
3648
|
+
if (errorDebounceTimer) {
|
|
3649
|
+
clearTimeout(errorDebounceTimer);
|
|
3650
|
+
errorDebounceTimer = null;
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
const swapToFallback = (reason) => {
|
|
3654
|
+
if (isUnmounted || hasSucceeded || fallbackRendered) return;
|
|
3655
|
+
fallbackRendered = true;
|
|
3656
|
+
clearTimers();
|
|
3657
|
+
runtime.events.publish("chatbot.fallback_rendered", {
|
|
3658
|
+
tileId: resolvedTileId,
|
|
3659
|
+
reason
|
|
3660
|
+
});
|
|
3661
|
+
transport?.disconnect();
|
|
3317
3662
|
el.remove();
|
|
3663
|
+
container.innerHTML = renderFallbackHtml(chatbotConfig.fallback);
|
|
3664
|
+
};
|
|
3665
|
+
const setupTransport = (cftToken) => {
|
|
3666
|
+
if (isUnmounted) return;
|
|
3667
|
+
transport = new SsePostTransport({
|
|
3668
|
+
sseUrl: buildSseUrl(cftToken),
|
|
3669
|
+
postUrl,
|
|
3670
|
+
headers: buildHeaders(cftToken),
|
|
3671
|
+
onA2UIEvent: (payload) => {
|
|
3672
|
+
runtime.actions.applyBatch([payload]).then(() => {
|
|
3673
|
+
runtime.events.publish("chatbot.a2ui_applied", {
|
|
3674
|
+
tileId: resolvedTileId
|
|
3675
|
+
});
|
|
3676
|
+
}).catch((err) => {
|
|
3677
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3678
|
+
console.error("[adaptive-chatbot] A2UI apply failed:", message);
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
el.transport = transport;
|
|
3683
|
+
unsubscribe = transport.subscribe((event) => {
|
|
3684
|
+
if (isUnmounted) return;
|
|
3685
|
+
if (event.type === "error") {
|
|
3686
|
+
if (hasSucceeded || errorDebounceTimer) return;
|
|
3687
|
+
errorDebounceTimer = setTimeout(() => {
|
|
3688
|
+
errorDebounceTimer = null;
|
|
3689
|
+
if (!hasSucceeded) swapToFallback("connect_failed");
|
|
3690
|
+
}, ERROR_DEBOUNCE_MS);
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
hasSucceeded = true;
|
|
3694
|
+
clearTimers();
|
|
3695
|
+
if (unsubscribe) {
|
|
3696
|
+
unsubscribe();
|
|
3697
|
+
unsubscribe = null;
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
container.appendChild(el);
|
|
3701
|
+
failureTimer = setTimeout(() => {
|
|
3702
|
+
if (!hasSucceeded) swapToFallback("connect_timeout");
|
|
3703
|
+
}, FAILURE_DETECTION_TIMEOUT_MS);
|
|
3704
|
+
};
|
|
3705
|
+
if (TURNSTILE_SITEKEY) {
|
|
3706
|
+
void acquireTurnstileToken().then(setupTransport);
|
|
3707
|
+
} else {
|
|
3708
|
+
setupTransport(null);
|
|
3709
|
+
}
|
|
3710
|
+
return () => {
|
|
3711
|
+
isUnmounted = true;
|
|
3712
|
+
clearTimers();
|
|
3713
|
+
if (unsubscribe) {
|
|
3714
|
+
unsubscribe();
|
|
3715
|
+
unsubscribe = null;
|
|
3716
|
+
}
|
|
3717
|
+
if (!fallbackRendered) {
|
|
3718
|
+
transport?.disconnect();
|
|
3719
|
+
el.remove();
|
|
3720
|
+
}
|
|
3318
3721
|
};
|
|
3319
3722
|
}
|
|
3320
3723
|
};
|
|
3321
3724
|
|
|
3322
3725
|
export {
|
|
3726
|
+
acquireTurnstileToken,
|
|
3323
3727
|
ChatAssistantLitMountable
|
|
3324
3728
|
};
|
|
3325
3729
|
/*! Bundled license information:
|
|
@@ -3327,4 +3731,4 @@ export {
|
|
|
3327
3731
|
dompurify/dist/purify.es.mjs:
|
|
3328
3732
|
(*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE *)
|
|
3329
3733
|
*/
|
|
3330
|
-
//# sourceMappingURL=chunk-
|
|
3734
|
+
//# sourceMappingURL=chunk-DUQTO5RE.js.map
|