@smooai/chat-widget 0.4.0 → 0.5.1
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/README.md +68 -14
- package/dist/chat-widget-loader.global.js +157 -0
- package/dist/chat-widget-loader.global.js.map +1 -0
- package/dist/chat-widget.global.js +24 -4
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
- package/src/index.ts +4 -0
- package/src/loader-core.ts +161 -0
- package/src/loader.ts +17 -0
package/README.md
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
<
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://smoo.ai"><img src=".github/banner.png" alt="@smooai/chat-widget — Embeddable AI chat as a web component" width="100%" /></a>
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@smooai/chat-widget"><img src="https://img.shields.io/npm/v/@smooai/chat-widget?style=for-the-badge&color=00A6A6&label=npm&logo=npm&logoColor=white&labelColor=020618" alt="npm"></a>
|
|
7
|
+
<a href="https://smoo.ai/platform/support"><img src="https://img.shields.io/badge/Smoo_AI-platform-00A6A6?style=for-the-badge&labelColor=020618" alt="Smoo AI"></a>
|
|
8
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-F49F0A?style=for-the-badge&labelColor=020618" alt="license"></a>
|
|
9
|
+
</p>
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://img.shields.io/badge/gzip-~19%20kB-FF6B6C?style=flat-square" alt="bundle size">
|
|
13
|
+
<a href="https://github.com/SmooAI/chat-widget/actions/workflows/ci.yml"><img src="https://github.com/SmooAI/chat-widget/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
14
|
+
</p>
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[](https://github.com/SmooAI/chat-widget/actions/workflows/ci.yml)
|
|
11
|
-
[](https://github.com/SmooAI/chat-widget)
|
|
12
|
-
[](./LICENSE)
|
|
13
|
-
|
|
14
|
-
</div>
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="#why-this-widget"><b>Features</b></a> · <a href="#install"><b>Install</b></a> · <a href="#quick-start"><b>Usage</b></a> · <a href="#theming"><b>Theming</b></a> · <a href="#part-of-smoo-ai"><b>Platform</b></a>
|
|
18
|
+
</p>
|
|
15
19
|
|
|
16
20
|
---
|
|
17
21
|
|
|
22
|
+
> A self-contained, streaming chat surface you drop into any site with one tag. No React on the host page, no CSS to import, ~19 kB gzipped, with the smooth-operator protocol client built in.
|
|
23
|
+
|
|
18
24
|
`<smooth-agent-chat>` is a self-contained chat web component for the
|
|
19
25
|
[smooth-operator](https://github.com/SmooAI/smooth-operator) protocol. Drop in one
|
|
20
26
|
`<script>` tag (or import the ESM module) and you get a polished, streaming,
|
|
@@ -60,7 +66,36 @@ installed automatically.
|
|
|
60
66
|
|
|
61
67
|
## Quick start
|
|
62
68
|
|
|
63
|
-
### 1.
|
|
69
|
+
### 1. Recommended — the deferred loader (best Core Web Vitals)
|
|
70
|
+
|
|
71
|
+
Include the tiny **loader** (~2 KB gzipped) eagerly. It does nothing expensive on
|
|
72
|
+
load — it waits until the page is past its critical render (`requestIdleCallback`
|
|
73
|
+
after `load`, **or** the visitor's first pointer/keydown/scroll, **or** an 8 s
|
|
74
|
+
fallback), and only *then* injects the full widget module. The widget never
|
|
75
|
+
competes with your page's LCP/TBT, but it's instantly there the moment a visitor
|
|
76
|
+
shows intent.
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<script>
|
|
80
|
+
window.SmoothAgentChatConfig = {
|
|
81
|
+
endpoint: 'wss://your-host/ws',
|
|
82
|
+
agentId: '11111111-1111-4111-8111-111111111111',
|
|
83
|
+
agentName: 'Support',
|
|
84
|
+
greeting: 'Hi! How can I help?',
|
|
85
|
+
};
|
|
86
|
+
</script>
|
|
87
|
+
<script src="https://unpkg.com/@smooai/chat-widget/dist/chat-widget-loader.global.js" async></script>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The loader resolves the widget module as a sibling of its own URL (override with
|
|
91
|
+
`data-src`), so a single CDN path is all you need. For the simplest installs you
|
|
92
|
+
can skip the config global and use `data-*` attributes on the loader tag
|
|
93
|
+
(`data-endpoint`, `data-agent-id`, `data-primary`, `data-mode`).
|
|
94
|
+
|
|
95
|
+
### 2. Declarative — eager `<script>` + the element
|
|
96
|
+
|
|
97
|
+
Simplest, but the module evaluates during page load — prefer the loader above on
|
|
98
|
+
performance-sensitive (especially marketing) pages.
|
|
64
99
|
|
|
65
100
|
```html
|
|
66
101
|
<script src="https://unpkg.com/@smooai/chat-widget/dist/chat-widget.global.js"></script>
|
|
@@ -75,7 +110,7 @@ installed automatically.
|
|
|
75
110
|
|
|
76
111
|
The IIFE bundle auto-registers the element on load.
|
|
77
112
|
|
|
78
|
-
###
|
|
113
|
+
### 3. Programmatic — bundler / ESM
|
|
79
114
|
|
|
80
115
|
```ts
|
|
81
116
|
import { mountChatWidget } from '@smooai/chat-widget';
|
|
@@ -235,6 +270,25 @@ pnpm test:e2e # Playwright live e2e (gated — see e2e/)
|
|
|
235
270
|
Open `index.html` (e.g. `pnpm dlx serve .`) after a build for the interactive,
|
|
236
271
|
backend-free showcase.
|
|
237
272
|
|
|
273
|
+
## Part of Smoo AI
|
|
274
|
+
|
|
275
|
+
`@smooai/chat-widget` is built and open-sourced by **[Smoo AI](https://smoo.ai)** — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.
|
|
276
|
+
|
|
277
|
+
- 🚀 **Customer support on the platform** — [smoo.ai/platform/support](https://smoo.ai/platform/support)
|
|
278
|
+
- 🧰 **More open source from Smoo AI** — [smoo.ai/open-source](https://smoo.ai/open-source)
|
|
279
|
+
- 🧩 **Sibling repos** — [smooth-operator](https://github.com/SmooAI/smooth-operator) (the protocol it renders), [smooth-operator-core](https://github.com/SmooAI/smooth-operator-core) (the engine), [@smooai/config](https://github.com/SmooAI/config), [@smooai/logger](https://github.com/SmooAI/logger)
|
|
280
|
+
|
|
281
|
+
## Contributing
|
|
282
|
+
|
|
283
|
+
Issues and PRs welcome. Run `pnpm check` (typecheck + test + build) before opening
|
|
284
|
+
a PR.
|
|
285
|
+
|
|
238
286
|
## License
|
|
239
287
|
|
|
240
|
-
MIT © [
|
|
288
|
+
MIT © [Smoo AI](https://smoo.ai)
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
<p align="center">
|
|
293
|
+
Built by <a href="https://smoo.ai"><strong>Smoo AI</strong></a> — AI built into every product.
|
|
294
|
+
</p>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
//#region src/loader-core.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tiny, dependency-free deferred loader for `@smooai/chat-widget`.
|
|
5
|
+
*
|
|
6
|
+
* The recommended embed: a host includes this *small* script eagerly, and it
|
|
7
|
+
* defers injecting the real (heavier) `chat-widget.global.js` module until the
|
|
8
|
+
* page is past its critical render — so the widget never competes with the host
|
|
9
|
+
* page's LCP/TBT. It triggers the real load on the **first** of:
|
|
10
|
+
*
|
|
11
|
+
* - `window` `load` → `requestIdleCallback` (idle time), or
|
|
12
|
+
* - user intent (`pointerdown` / `keydown` / `scroll`), or
|
|
13
|
+
* - an 8s fallback.
|
|
14
|
+
*
|
|
15
|
+
* After the module loads (which registers `<smooth-agent-chat>` and exposes
|
|
16
|
+
* `window.SmoothAgentChat`), it mounts the widget with the host's config.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately imports nothing from the widget itself — this file must stay a
|
|
19
|
+
* few hundred bytes so including it eagerly is free.
|
|
20
|
+
*
|
|
21
|
+
* ## Config
|
|
22
|
+
* Set a global before/with the loader (any `ChatWidgetConfig`, plus an optional
|
|
23
|
+
* `src` pointing at the module bundle):
|
|
24
|
+
*
|
|
25
|
+
* ```html
|
|
26
|
+
* <script>window.SmoothAgentChatConfig = { endpoint: 'wss://…/ws', agentId: '…' };<\/script>
|
|
27
|
+
* <script src="https://cdn/…/chat-widget-loader.global.js" async><\/script>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Or, for the simplest installs, `data-*` attributes on the loader's own tag
|
|
31
|
+
* (`data-endpoint`, `data-agent-id`, `data-primary`, `data-mode`, `data-src`).
|
|
32
|
+
* If no config is found, the module is still loaded (registering the element) so
|
|
33
|
+
* a `<smooth-agent-chat …>` placed directly in markup upgrades itself.
|
|
34
|
+
*/
|
|
35
|
+
/** Must match `ELEMENT_TAG` in `element.ts`; inlined to avoid importing it. */
|
|
36
|
+
const ELEMENT_TAG = "smooth-agent-chat";
|
|
37
|
+
/** Default module filename, resolved as a sibling of the loader script. */
|
|
38
|
+
const DEFAULT_MODULE = "chat-widget.global.js";
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the module URL: explicit `src` (config or `data-src`) wins, else a
|
|
41
|
+
* sibling of the loader script, else the bare default filename.
|
|
42
|
+
*/
|
|
43
|
+
function resolveModuleSrc(win, script) {
|
|
44
|
+
const fromConfig = win.SmoothAgentChatConfig?.src;
|
|
45
|
+
if (fromConfig) return fromConfig;
|
|
46
|
+
const fromAttr = script?.getAttribute("data-src");
|
|
47
|
+
if (fromAttr) return fromAttr;
|
|
48
|
+
const selfSrc = script?.src;
|
|
49
|
+
if (selfSrc) return selfSrc.replace(/[^/]*$/, DEFAULT_MODULE);
|
|
50
|
+
return DEFAULT_MODULE;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The widget mount config: the `SmoothAgentChatConfig` global (minus the
|
|
54
|
+
* loader-only `src`), or `data-*` attributes. `undefined` ⇒ no programmatic
|
|
55
|
+
* mount (a markup element will upgrade itself once the module registers it).
|
|
56
|
+
*/
|
|
57
|
+
function resolveMountConfig(win, script) {
|
|
58
|
+
if (win.SmoothAgentChatConfig) {
|
|
59
|
+
const { src: _src, ...config } = win.SmoothAgentChatConfig;
|
|
60
|
+
return Object.keys(config).length > 0 ? config : void 0;
|
|
61
|
+
}
|
|
62
|
+
if (!script) return void 0;
|
|
63
|
+
const endpoint = script.getAttribute("data-endpoint");
|
|
64
|
+
const agentId = script.getAttribute("data-agent-id");
|
|
65
|
+
if (!endpoint && !agentId) return void 0;
|
|
66
|
+
const config = {};
|
|
67
|
+
if (endpoint) config.endpoint = endpoint;
|
|
68
|
+
if (agentId) config.agentId = agentId;
|
|
69
|
+
const primary = script.getAttribute("data-primary");
|
|
70
|
+
if (primary) config.theme = { primary };
|
|
71
|
+
const mode = script.getAttribute("data-mode");
|
|
72
|
+
if (mode) config.mode = mode;
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Install the deferred loader. Idempotent per call; the IIFE entry
|
|
77
|
+
* (`loader.ts`) invokes it once on script execution.
|
|
78
|
+
*/
|
|
79
|
+
function initChatWidgetLoader() {
|
|
80
|
+
const win = window;
|
|
81
|
+
const script = document.currentScript ?? null;
|
|
82
|
+
const moduleSrc = resolveModuleSrc(win, script);
|
|
83
|
+
let loaded = false;
|
|
84
|
+
let idleId;
|
|
85
|
+
let fallbackId;
|
|
86
|
+
function mountWhenReady() {
|
|
87
|
+
const config = resolveMountConfig(win, script);
|
|
88
|
+
if (!config) return;
|
|
89
|
+
const apply = () => {
|
|
90
|
+
try {
|
|
91
|
+
win.SmoothAgentChat?.mount(config);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("[chat-widget loader] mount failed", error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (win.SmoothAgentChat) apply();
|
|
97
|
+
else if (window.customElements?.whenDefined) window.customElements.whenDefined(ELEMENT_TAG).then(apply, apply);
|
|
98
|
+
else apply();
|
|
99
|
+
}
|
|
100
|
+
function teardown() {
|
|
101
|
+
if (idleId !== void 0) {
|
|
102
|
+
window.cancelIdleCallback?.(idleId);
|
|
103
|
+
window.clearTimeout(idleId);
|
|
104
|
+
}
|
|
105
|
+
if (fallbackId !== void 0) window.clearTimeout(fallbackId);
|
|
106
|
+
window.removeEventListener("pointerdown", load);
|
|
107
|
+
window.removeEventListener("keydown", load);
|
|
108
|
+
window.removeEventListener("scroll", load);
|
|
109
|
+
}
|
|
110
|
+
function load() {
|
|
111
|
+
if (loaded) return;
|
|
112
|
+
loaded = true;
|
|
113
|
+
teardown();
|
|
114
|
+
const tag = document.createElement("script");
|
|
115
|
+
tag.src = moduleSrc;
|
|
116
|
+
tag.onload = mountWhenReady;
|
|
117
|
+
tag.onerror = () => console.error("[chat-widget loader] failed to load", moduleSrc);
|
|
118
|
+
document.head.appendChild(tag);
|
|
119
|
+
}
|
|
120
|
+
function schedule() {
|
|
121
|
+
if (window.requestIdleCallback) idleId = window.requestIdleCallback(() => load(), { timeout: 5e3 });
|
|
122
|
+
else idleId = window.setTimeout(load, 2500);
|
|
123
|
+
fallbackId = window.setTimeout(load, 8e3);
|
|
124
|
+
}
|
|
125
|
+
window.addEventListener("pointerdown", load, {
|
|
126
|
+
once: true,
|
|
127
|
+
passive: true
|
|
128
|
+
});
|
|
129
|
+
window.addEventListener("keydown", load, { once: true });
|
|
130
|
+
window.addEventListener("scroll", load, {
|
|
131
|
+
once: true,
|
|
132
|
+
passive: true
|
|
133
|
+
});
|
|
134
|
+
if (document.readyState === "complete") schedule();
|
|
135
|
+
else window.addEventListener("load", schedule, { once: true });
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/loader.ts
|
|
139
|
+
/**
|
|
140
|
+
* Standalone IIFE entry for the **deferred loader** — built to
|
|
141
|
+
* `dist/chat-widget-loader.global.js`.
|
|
142
|
+
*
|
|
143
|
+
* This is the recommended embed: include it eagerly (it's tiny) and it lazily
|
|
144
|
+
* injects the real `chat-widget.global.js` module only once the host page is
|
|
145
|
+
* past its critical render (idle / user intent / 8s fallback), so the widget
|
|
146
|
+
* never competes with the host's LCP/TBT. See {@link initChatWidgetLoader}.
|
|
147
|
+
*
|
|
148
|
+
* ```html
|
|
149
|
+
* <script>window.SmoothAgentChatConfig = { endpoint: 'wss://…/ws', agentId: '…' };<\/script>
|
|
150
|
+
* <script src="https://cdn/…/chat-widget-loader.global.js" async><\/script>
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
initChatWidgetLoader();
|
|
154
|
+
//#endregion
|
|
155
|
+
})();
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=chat-widget-loader.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-widget-loader.global.js","names":[],"sources":["../src/loader-core.ts","../src/loader.ts"],"sourcesContent":["/**\n * Tiny, dependency-free deferred loader for `@smooai/chat-widget`.\n *\n * The recommended embed: a host includes this *small* script eagerly, and it\n * defers injecting the real (heavier) `chat-widget.global.js` module until the\n * page is past its critical render — so the widget never competes with the host\n * page's LCP/TBT. It triggers the real load on the **first** of:\n *\n * - `window` `load` → `requestIdleCallback` (idle time), or\n * - user intent (`pointerdown` / `keydown` / `scroll`), or\n * - an 8s fallback.\n *\n * After the module loads (which registers `<smooth-agent-chat>` and exposes\n * `window.SmoothAgentChat`), it mounts the widget with the host's config.\n *\n * Deliberately imports nothing from the widget itself — this file must stay a\n * few hundred bytes so including it eagerly is free.\n *\n * ## Config\n * Set a global before/with the loader (any `ChatWidgetConfig`, plus an optional\n * `src` pointing at the module bundle):\n *\n * ```html\n * <script>window.SmoothAgentChatConfig = { endpoint: 'wss://…/ws', agentId: '…' };</script>\n * <script src=\"https://cdn/…/chat-widget-loader.global.js\" async></script>\n * ```\n *\n * Or, for the simplest installs, `data-*` attributes on the loader's own tag\n * (`data-endpoint`, `data-agent-id`, `data-primary`, `data-mode`, `data-src`).\n * If no config is found, the module is still loaded (registering the element) so\n * a `<smooth-agent-chat …>` placed directly in markup upgrades itself.\n */\n\n/** Must match `ELEMENT_TAG` in `element.ts`; inlined to avoid importing it. */\nconst ELEMENT_TAG = 'smooth-agent-chat';\n/** Default module filename, resolved as a sibling of the loader script. */\nconst DEFAULT_MODULE = 'chat-widget.global.js';\n\ntype MountFn = (config: Record<string, unknown>, target?: HTMLElement) => unknown;\n\ninterface LoaderWindow extends Window {\n SmoothAgentChat?: { mount: MountFn };\n SmoothAgentChatConfig?: Record<string, unknown> & { src?: string };\n}\n\n/**\n * Resolve the module URL: explicit `src` (config or `data-src`) wins, else a\n * sibling of the loader script, else the bare default filename.\n */\nfunction resolveModuleSrc(win: LoaderWindow, script: HTMLScriptElement | null): string {\n const fromConfig = win.SmoothAgentChatConfig?.src;\n if (fromConfig) return fromConfig;\n const fromAttr = script?.getAttribute('data-src');\n if (fromAttr) return fromAttr;\n const selfSrc = script?.src;\n if (selfSrc) return selfSrc.replace(/[^/]*$/, DEFAULT_MODULE);\n return DEFAULT_MODULE;\n}\n\n/**\n * The widget mount config: the `SmoothAgentChatConfig` global (minus the\n * loader-only `src`), or `data-*` attributes. `undefined` ⇒ no programmatic\n * mount (a markup element will upgrade itself once the module registers it).\n */\nfunction resolveMountConfig(win: LoaderWindow, script: HTMLScriptElement | null): Record<string, unknown> | undefined {\n if (win.SmoothAgentChatConfig) {\n const { src: _src, ...config } = win.SmoothAgentChatConfig;\n // Only `src` (no real fields) ⇒ no programmatic mount; a markup element\n // upgrades itself once the module registers it.\n return Object.keys(config).length > 0 ? config : undefined;\n }\n if (!script) return undefined;\n const endpoint = script.getAttribute('data-endpoint');\n const agentId = script.getAttribute('data-agent-id');\n if (!endpoint && !agentId) return undefined;\n const config: Record<string, unknown> = {};\n if (endpoint) config.endpoint = endpoint;\n if (agentId) config.agentId = agentId;\n const primary = script.getAttribute('data-primary');\n if (primary) config.theme = { primary };\n const mode = script.getAttribute('data-mode');\n if (mode) config.mode = mode;\n return config;\n}\n\n/**\n * Install the deferred loader. Idempotent per call; the IIFE entry\n * (`loader.ts`) invokes it once on script execution.\n */\nexport function initChatWidgetLoader(): void {\n const win = window as LoaderWindow;\n const script = (document.currentScript as HTMLScriptElement | null) ?? null;\n const moduleSrc = resolveModuleSrc(win, script);\n\n let loaded = false;\n let idleId: number | undefined;\n let fallbackId: number | undefined;\n\n function mountWhenReady(): void {\n const config = resolveMountConfig(win, script);\n if (!config) return; // markup element upgrades itself; nothing to mount\n const apply = () => {\n try {\n win.SmoothAgentChat?.mount(config);\n } catch (error) {\n console.error('[chat-widget loader] mount failed', error);\n }\n };\n if (win.SmoothAgentChat) {\n apply();\n } else if (window.customElements?.whenDefined) {\n window.customElements.whenDefined(ELEMENT_TAG).then(apply, apply);\n } else {\n apply();\n }\n }\n\n function teardown(): void {\n if (idleId !== undefined) {\n // The id is either an idle handle or (fallback path) a timeout id;\n // both cancels are no-ops on the wrong kind, so calling both is safe.\n window.cancelIdleCallback?.(idleId);\n window.clearTimeout(idleId);\n }\n if (fallbackId !== undefined) window.clearTimeout(fallbackId);\n window.removeEventListener('pointerdown', load);\n window.removeEventListener('keydown', load);\n window.removeEventListener('scroll', load);\n }\n\n function load(): void {\n if (loaded) return;\n loaded = true;\n teardown();\n const tag = document.createElement('script');\n tag.src = moduleSrc;\n tag.onload = mountWhenReady;\n tag.onerror = () => console.error('[chat-widget loader] failed to load', moduleSrc);\n document.head.appendChild(tag);\n }\n\n function schedule(): void {\n if (window.requestIdleCallback) {\n idleId = window.requestIdleCallback(() => load(), { timeout: 5000 });\n } else {\n idleId = window.setTimeout(load, 2500) as unknown as number;\n }\n fallbackId = window.setTimeout(load, 8000) as unknown as number;\n }\n\n // User intent loads immediately (they're about to want the widget).\n window.addEventListener('pointerdown', load, { once: true, passive: true });\n window.addEventListener('keydown', load, { once: true });\n window.addEventListener('scroll', load, { once: true, passive: true });\n\n if (document.readyState === 'complete') {\n schedule();\n } else {\n window.addEventListener('load', schedule, { once: true });\n }\n}\n","/**\n * Standalone IIFE entry for the **deferred loader** — built to\n * `dist/chat-widget-loader.global.js`.\n *\n * This is the recommended embed: include it eagerly (it's tiny) and it lazily\n * injects the real `chat-widget.global.js` module only once the host page is\n * past its critical render (idle / user intent / 8s fallback), so the widget\n * never competes with the host's LCP/TBT. See {@link initChatWidgetLoader}.\n *\n * ```html\n * <script>window.SmoothAgentChatConfig = { endpoint: 'wss://…/ws', agentId: '…' };</script>\n * <script src=\"https://cdn/…/chat-widget-loader.global.js\" async></script>\n * ```\n */\nimport { initChatWidgetLoader } from './loader-core.js';\n\ninitChatWidgetLoader();\n"],"mappings":"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"}
|
|
@@ -49,7 +49,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
49
49
|
return !resolved.allowAnonymous && (resolved.requireName || resolved.requireEmail || resolved.requirePhone);
|
|
50
50
|
}
|
|
51
51
|
//#endregion
|
|
52
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@
|
|
52
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/transport.js
|
|
53
53
|
/**
|
|
54
54
|
* Transport abstraction for the client.
|
|
55
55
|
*
|
|
@@ -174,7 +174,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
//#endregion
|
|
177
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@
|
|
177
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/types.js
|
|
178
178
|
/** Every server→client `type` discriminator value. */
|
|
179
179
|
const EVENT_TYPES = [
|
|
180
180
|
"immediate_response",
|
|
@@ -195,7 +195,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
195
195
|
return typeof frame === "object" && frame !== null && "type" in frame && typeof frame.type === "string" && EVENT_TYPES.includes(frame.type);
|
|
196
196
|
}
|
|
197
197
|
//#endregion
|
|
198
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@
|
|
198
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/client.js
|
|
199
199
|
/**
|
|
200
200
|
* SmoothAgentClient — a minimal, idiomatic, transport-agnostic client for the
|
|
201
201
|
* smooth-operator WebSocket protocol.
|
|
@@ -371,7 +371,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
371
371
|
listeners = /* @__PURE__ */ new Set();
|
|
372
372
|
unsubscribe = [];
|
|
373
373
|
constructor(options) {
|
|
374
|
-
this.transport = options.transport ?? new WebSocketTransport(options.url, options.webSocketFactory);
|
|
374
|
+
this.transport = options.transport ?? new WebSocketTransport(withConnectionToken(options.url, options.token), options.webSocketFactory);
|
|
375
375
|
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
376
376
|
this.turnTimeout = options.turnTimeout ?? 12e4;
|
|
377
377
|
this.generateRequestId = options.generateRequestId ?? (() => `req-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`);
|
|
@@ -526,6 +526,26 @@ var SmoothAgentChat = (function(exports) {
|
|
|
526
526
|
this.turns.clear();
|
|
527
527
|
}
|
|
528
528
|
};
|
|
529
|
+
/**
|
|
530
|
+
* Merge a connection auth `token` into a WebSocket URL as a `?token=` query param,
|
|
531
|
+
* preserving any existing query string. Returns `url` unchanged when no token is
|
|
532
|
+
* given, so the no-token path is byte-for-byte identical to before. Uses `URL` /
|
|
533
|
+
* `URLSearchParams` so an existing `?foo=bar` becomes `?foo=bar&token=…` (and the
|
|
534
|
+
* value is properly percent-encoded) rather than a naive `?`/`&` string-concat.
|
|
535
|
+
*
|
|
536
|
+
* Falls back to manual concatenation if `url` is not absolute (so `URL` can't parse
|
|
537
|
+
* it) — e.g. a relative or mock URL used in tests.
|
|
538
|
+
*/
|
|
539
|
+
function withConnectionToken(url, token) {
|
|
540
|
+
if (!token) return url;
|
|
541
|
+
try {
|
|
542
|
+
const parsed = new URL(url);
|
|
543
|
+
parsed.searchParams.set("token", token);
|
|
544
|
+
return parsed.toString();
|
|
545
|
+
} catch {
|
|
546
|
+
return `${url}${url.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
529
549
|
/** Pull the typed `data` payload out of an `immediate_response` event. */
|
|
530
550
|
function extractImmediateData(event) {
|
|
531
551
|
if (event.type === "immediate_response") return event.data;
|