dewee-webchat 0.4.1 → 0.5.0-beta.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 +103 -7
- package/astro/DeweeChat.astro +2 -2
- package/dist/{chunk-VU2M42GA.js → chunk-M6J3IXEI.js} +669 -73
- package/dist/{chunk-EVZ5K7BN.js → chunk-WHHQ6FKK.js} +1 -1
- package/dist/dewee-webchat.js +37 -30
- package/dist/{handler-DunrbnSD.d.ts → handler-B-c6tDr7.d.ts} +50 -5
- package/dist/index.d.ts +56 -7
- package/dist/index.js +4 -2
- package/dist/page-context-H3ZROFAV.js +137 -0
- package/dist/{protocol-CLbGE9Ns.d.ts → protocol-BbjGSfZa.d.ts} +21 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +9 -7
- package/dist/server-identity.d.ts +2 -2
- package/dist/server.d.ts +7 -4
- package/dist/server.js +631 -61
- package/dist/vue.d.ts +68 -3
- package/dist/vue.js +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ first three values below.
|
|
|
56
56
|
| `DEWEE_CCP_CHAT_URL` | The ingress URL, `https://<ccp>/api/public/webchat/v1/wci_…`. https only (plain http to `localhost` for development). |
|
|
57
57
|
| `DEWEE_WEBCHAT_INTEGRATION_ID` | `wci_…`; must match the end of `DEWEE_CCP_CHAT_URL` |
|
|
58
58
|
| `DEWEE_WEBCHAT_INTEGRATION_SECRET` | `dwc_…`. **Server only.** Sent only to the `DEWEE_CCP_CHAT_URL` origin, never across a redirect. |
|
|
59
|
-
| `DEWEE_WEBCHAT_SECRET` | 32
|
|
59
|
+
| `DEWEE_WEBCHAT_SECRET` | At least 32 random UTF-8 bytes to sign visitor sessions (`openssl rand -base64 32`) |
|
|
60
60
|
| `DEWEE_ALLOWED_ORIGINS` | Extra origins for cross-origin embedding (same origin always works) |
|
|
61
61
|
| `TURNSTILE_SITE_KEY` / `TURNSTILE_SECRET_KEY` | Optional Cloudflare Turnstile check for new visitors (with `ccp`, a returning visitor's hourly refresh is not challenged) |
|
|
62
62
|
| `DEWEE_WEBCHAT_TRANSPORT` | Optional: `ccp` or `legacy-gateway`. Without it, the variables that are set decide (gateway variables alone select `legacy-gateway`, with a deprecation warning); set it to pin the transport explicitly. |
|
|
@@ -85,8 +85,26 @@ The two sets cannot be mixed: with CCP and gateway variables both set, or `trans
|
|
|
85
85
|
the other's variables set, the handler refuses to start rather than send a credential to an origin
|
|
86
86
|
you did not pick. `agents`, `defaultAgent` and `allowClientCredentials` are refused with `ccp`.
|
|
87
87
|
|
|
88
|
+
Both transports need a stable `secret` shared by every backend instance. For local development only,
|
|
89
|
+
the legacy transport can opt in to a generated process-local secret with `allowEphemeralSecret: true`.
|
|
90
|
+
Sessions and identity bindings then stop working after a restart or when a request reaches another
|
|
91
|
+
instance. CCP always requires a configured secret of at least 32 bytes.
|
|
92
|
+
|
|
88
93
|
## 2. Add the widget
|
|
89
94
|
|
|
95
|
+
### Optional current-page context
|
|
96
|
+
|
|
97
|
+
Page context is off by default. Set `page-context="metadata"` or `page-context="content"` on the
|
|
98
|
+
widget (or `pageContext` in framework configuration) to offer it. The visitor previews the page
|
|
99
|
+
and explicitly chooses whether to share it for each message. Only a supported Gateway/CCP version
|
|
100
|
+
receives the attachment; ordinary chat continues when support is absent. For content, the default
|
|
101
|
+
source is `main`, `article` or `[role=main]`; `contextSelector` and `excludeSelectors` narrow it.
|
|
102
|
+
Form controls, editable areas and hidden content are excluded. Configure a client-side
|
|
103
|
+
`contextProvider` for host-filtered data when needed; the host is responsible for excluding private
|
|
104
|
+
data from its provider. In Astro, set that function with `element.configure()` in a client script,
|
|
105
|
+
not as a serialized component prop. The preview contains the data to be sent: review it before
|
|
106
|
+
sharing. See the [HTTP contract](https://github.com/nextlevelbuilder/dewee-webchat/blob/main/docs/http-contract.md#page-context-attachment).
|
|
107
|
+
|
|
90
108
|
**Script tag** (no build step):
|
|
91
109
|
|
|
92
110
|
```html
|
|
@@ -140,6 +158,7 @@ Attributes use kebab-case (`show-reasoning="false"`), `data-*` on the script tag
|
|
|
140
158
|
| `title`, `subtitle`, `greeting`, `intro`, `placeholder` | | Texts |
|
|
141
159
|
| `suggestions` | | Quick replies (`a|b` or JSON array as attribute) |
|
|
142
160
|
| `avatar`, `launcherAvatar` | | Image URL; also use it on the launcher |
|
|
161
|
+
| `imageOrigins` | `[]` | Additional trusted HTTPS origins for Markdown images |
|
|
143
162
|
| `mode` | `floating` | `floating` (bubble + panel) or `inline` (fills its container) |
|
|
144
163
|
| `position` | `bottom-right` | or `bottom-left` |
|
|
145
164
|
| `theme` | `light` | `light`, `dark`, `auto` |
|
|
@@ -153,6 +172,8 @@ Attributes use kebab-case (`show-reasoning="false"`), `data-*` on the script tag
|
|
|
153
172
|
| `actions`, `confirm`, `getPageContext` | | [Page actions](#agent-page-actions) (JS/props only) |
|
|
154
173
|
| `maxActionRounds` | `5` | Automatic action turns per visitor message |
|
|
155
174
|
| `actionTimeoutMs` | `30000` | Per-handler timeout |
|
|
175
|
+
| `domActions` | `false` | [Built-in DOM actions](#built-in-dom-actions): snapshot, scroll, click, fill |
|
|
176
|
+
| `domActionsRoot` | `document.body` | Selector or element the built-in DOM actions are limited to |
|
|
156
177
|
| `headers` | | Extra request headers, object or `() => object \| Promise<object>` called before every request (JS/props only) |
|
|
157
178
|
| `fetch` | `globalThis.fetch` | Custom fetch for every widget request (JS/props only) |
|
|
158
179
|
|
|
@@ -162,6 +183,33 @@ Methods: `open()`, `close()`, `toggle()`, `send(text)`, `stop()`, `newConversati
|
|
|
162
183
|
Events: `dewee:ready`, `dewee:open`, `dewee:close`, `dewee:send`, `dewee:reply`, `dewee:error`, `dewee:action`, `dewee:link`.
|
|
163
184
|
CSS variables: `--dw-accent`, `--dw-font`, `--dw-radius`, `--dw-width`, `--dw-height`, `--dw-z`.
|
|
164
185
|
|
|
186
|
+
## Markdown images
|
|
187
|
+
|
|
188
|
+
Replies may include `` or a same-origin relative image
|
|
189
|
+
URL. Images are disabled until HTTPS directory prefixes are approved in the backend. Configure the backend
|
|
190
|
+
with `imageSources: ["https://cdn.example.com/products/", "https://qr.example.com/images/"]` (or the comma-separated
|
|
191
|
+
`DEWEE_IMAGE_SOURCES` environment variable). Include a safe image directory on your site's HTTPS origin for relative images.
|
|
192
|
+
For external origins, also configure the widget:
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<dewee-chat image-origins='["https://cdn.example.com", "https://qr.example.com"]'></dewee-chat>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The script-tag equivalent is `data-image-origins`; vanilla JS uses
|
|
199
|
+
`chat.configure({ imageOrigins: ["https://cdn.example.com"] })`, and React, Vue and Astro accept the
|
|
200
|
+
`imageOrigins` prop. Unapproved or broken images fall back to alt text. The widget loads images from
|
|
201
|
+
`{endpoint}/image` using a short-lived encrypted ticket issued to the visitor session; the backend makes the request without cookies or Gateway credentials, rejects
|
|
202
|
+
redirects, and accepts PNG, JPEG, GIF and WebP only after validating the content type and file
|
|
203
|
+
signature. Node and PHP pin a checked public DNS address; Cloudflare Workers use the platform's
|
|
204
|
+
network boundary. Workers conservatively refuse sources sharing the endpoint's last two hostname
|
|
205
|
+
labels (including sibling subdomains), to avoid bypassing Cloudflare's own-zone protections; host
|
|
206
|
+
images on a separate approved domain there. Files are limited to 2 MiB and 4096
|
|
207
|
+
pixels per edge, and responses are not cached. Animated GIF/PNG/WebP are refused. Raw HTML,
|
|
208
|
+
SVG, unsafe schemes, credentials in URLs and unapproved origins are rejected.
|
|
209
|
+
|
|
210
|
+
The proxy URL contains an encrypted, short-lived ticket, not the source URL or the visitor session.
|
|
211
|
+
Avoid secrets in image URLs: the ticket request contains the source URL in its body.
|
|
212
|
+
|
|
165
213
|
## Custom headers and fetch
|
|
166
214
|
|
|
167
215
|
Behind a same-origin BFF that wants a CSRF header, cookies or tracing:
|
|
@@ -178,12 +226,12 @@ Vanilla: `chat.configure({ headers, fetch })` or the `chat.headers` / `chat.fetc
|
|
|
178
226
|
every request to `endpoint` (config, session, history, link, chat). It cannot override `content-type`,
|
|
179
227
|
`authorization` (which carries the anonymous or signed-in session token), `x-dewee-gateway-url` or
|
|
180
228
|
`x-dewee-gateway-token` (case-insensitive; such keys are dropped). Header names are sent lower-cased. For a
|
|
181
|
-
cross-origin `endpoint`, the backend's
|
|
182
|
-
|
|
229
|
+
cross-origin `endpoint`, configure the backend's `allowedOrigins` and `allowedHeaders: ["x-csrf-token"]`.
|
|
230
|
+
The built-in TS and PHP handlers accept header names containing letters, digits and hyphens. A custom `fetch` receives every request as sent, including the session bearer token of a
|
|
183
231
|
signed-in user, so only the trusted host page should supply it.
|
|
184
232
|
|
|
185
233
|
Anonymous visitor sessions are reused until less than 10% of the token lifetime remains (at most 24 h, at least
|
|
186
|
-
30 s), so backends with short session TTLs do not mint a new session on every request. With `transport: "ccp"`,
|
|
234
|
+
30 s, and no more than half the lifetime), so backends with short session TTLs do not mint a new session on every request. With `transport: "ccp"`,
|
|
187
235
|
`/session` reports the end of the CCP edge session sealed in the token as `expiresAt` (and `expiresIn`), so the
|
|
188
236
|
widget asks `/session` again before it lapses, about once an hour, although the token itself lives 30 days. That
|
|
189
237
|
refresh keeps the visitor id and re-seals a refreshed edge session, so a host that keeps no memory between
|
|
@@ -253,6 +301,43 @@ or inserts it as HTML, caps calls per answer and automatic turns per message, an
|
|
|
253
301
|
`mutate`/`destructive` handler without approval. Page context and results are labelled as data for the
|
|
254
302
|
agent, but prompt injection through page content is still possible — do not put secrets in either.
|
|
255
303
|
|
|
304
|
+
### Built-in DOM actions
|
|
305
|
+
|
|
306
|
+
Without writing handlers, a page can let the agent scroll, click and type on it. They are off by
|
|
307
|
+
default:
|
|
308
|
+
|
|
309
|
+
```html
|
|
310
|
+
<script src="https://webchat-cdn.dewee.sh/v/latest/dewee-webchat.js"
|
|
311
|
+
data-endpoint="/api/dewee" data-dom-actions="true" data-dom-actions-root="#app" defer></script>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
<DeweeChat endpoint="/api/dewee" domActions domActionsRoot="#app" />
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
| Action | Sensitivity | What it does |
|
|
319
|
+
|---|---|---|
|
|
320
|
+
| `page.snapshot` | `read` | Lists visible links, buttons and form fields as `e3 textbox "Email" value="…"`, with a `generation` |
|
|
321
|
+
| `page.scroll` | `read` | Scrolls one screen up/down, to the top/bottom, or an element (`ref`) into view |
|
|
322
|
+
| `page.click` | `mutate` | Clicks an element by `ref` + `generation` |
|
|
323
|
+
| `page.fill` | `mutate` | Sets the value of a text field, textarea, select or editable area, then fires `input` and `change` |
|
|
324
|
+
|
|
325
|
+
- `page.click` and `page.fill` always ask the visitor first; the dialog (or your `confirm` hook, as
|
|
326
|
+
`args.target`) names the element, e.g. `button "Delete run"`.
|
|
327
|
+
- Refs come only from the latest snapshot. A ref from an older snapshot, or an element that is gone,
|
|
328
|
+
hidden or now sensitive, is refused, and the agent is told to take a new snapshot.
|
|
329
|
+
- Never listed or touched: password, payment card and one-time-code fields (by `type`, `autocomplete`
|
|
330
|
+
and name), hidden elements, the widget itself, and anything inside `[data-dewee-ignore]`. Put
|
|
331
|
+
`data-dewee-ignore` on whatever else the agent must not reach.
|
|
332
|
+
- `page.fill` uses the native value setter, so React and Vue controlled inputs see the change. Events are
|
|
333
|
+
synthetic (`isTrusted` is false): pages that require trusted events, file pickers and popups opened by a
|
|
334
|
+
click will not react.
|
|
335
|
+
- Your own actions keep working; registering an action with a built-in name (say `page.click`)
|
|
336
|
+
replaces the built-in one.
|
|
337
|
+
- The built-ins operate the DOM, not your app's permissions: the agent can do whatever the visitor's
|
|
338
|
+
clicks can, after approval. Narrow it with `domActionsRoot` and `data-dewee-ignore`, and keep the server
|
|
339
|
+
as the authority.
|
|
340
|
+
|
|
256
341
|
## Signed-in users
|
|
257
342
|
|
|
258
343
|
When your site knows who the visitor is, the agent can know it too, with private tools that act for
|
|
@@ -355,7 +440,7 @@ How it behaves:
|
|
|
355
440
|
memory, have the logout page send the widget's hint (`chat.reuseHint()`) along: the session in it
|
|
356
441
|
is revoked when it belongs to that subject. Pass the logout request so CCP sees the visitor's
|
|
357
442
|
origin (an integration with an origin allowlist needs it; without a request, the origin the
|
|
358
|
-
session was issued for is used).
|
|
443
|
+
session was issued for is used). Without a shared store, sessions held only by other instances end when they expire. The
|
|
359
444
|
legacy gateway has no revoke call, so there it only forgets them.
|
|
360
445
|
|
|
361
446
|
```ts
|
|
@@ -367,6 +452,14 @@ How it behaves:
|
|
|
367
452
|
// …then clear your own session cookie
|
|
368
453
|
}
|
|
369
454
|
```
|
|
455
|
+
- For multiple handler instances, supply `identity.sessionStore` implementing the exported
|
|
456
|
+
`IdentitySessionStore` interface. Share the same protected store and visitor secret across those
|
|
457
|
+
instances. The default is a bounded, per-handler in-memory store. Store records contain bearer
|
|
458
|
+
credentials: encrypt them at rest, restrict access and never log values. The interface requires
|
|
459
|
+
atomic revision checks and updates across instances, unique non-reused revisions even after
|
|
460
|
+
eviction, conditional token deletion, and expiry by `deadline`; a simple non-atomic KV get/set is
|
|
461
|
+
insufficient. Keys use scoped HMAC digests, not raw host user IDs. Store failures refuse session
|
|
462
|
+
issuance; logout remains best effort, logs store failure and still attempts the supplied hint.
|
|
370
463
|
- Fail closed. If `authenticate` returns `null`, the visitor chats anonymously, exactly as without
|
|
371
464
|
`identity`, unless `required` is set, in which case no session is issued. If it throws, the answer
|
|
372
465
|
is `503 SOURCE_UNAVAILABLE` and no session is issued. If the exchange for a signed-in user fails, no
|
|
@@ -412,8 +505,8 @@ links on its own. `dewee:link` fires with `{ expiresAt, deepLinks }`. A link tha
|
|
|
412
505
|
progress answers `409 link_in_progress`, a reused idempotency key `409 idempotency_key_reused`, and a
|
|
413
506
|
gateway with linking switched off `503 linking_unavailable`; the widget shows its own text for each.
|
|
414
507
|
|
|
415
|
-
The PHP backend
|
|
416
|
-
|
|
508
|
+
The PHP backend also supports signed-in users and a server-side logout hook; see the
|
|
509
|
+
[PHP identity setup](../../php/README.md#signed-in-users). Its stateless reuse uses the CCP reuse hint.
|
|
417
510
|
|
|
418
511
|
## Security model
|
|
419
512
|
|
|
@@ -429,6 +522,9 @@ the Node/Workers handler. It refuses tokens that carry an identity session.
|
|
|
429
522
|
- Only allowed agents are reachable (the integration's, legacy: `agents`); origin allowlist, rate limits (20 messages/min per visitor, 10
|
|
430
523
|
new sessions/10 min per IP by default — pass `rateLimit` for a shared store), 4000-char messages,
|
|
431
524
|
optional Turnstile.
|
|
525
|
+
- The default rate limiter is in memory and applies per handler instance, not across replicas or
|
|
526
|
+
serverless invocations. For a shared backend limit, provide an atomic shared-store `rateLimit`
|
|
527
|
+
implementation; do not rely on the default as a cluster-wide limit.
|
|
432
528
|
- The Dewee gateway enforces the shared admission limits on every message. A refusal reaches the widget
|
|
433
529
|
as a typed SSE error (`POLICY_DENIED`, or `RATE_LIMITED` with `retryAfterMs`) with a fixed message, never
|
|
434
530
|
the gateway's own text. The widget shows a localized error and disables sending until `retryAfterMs`
|
package/astro/DeweeChat.astro
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Astro component: <DeweeChat endpoint="/api/dewee" title="Support" showReasoning={false} />
|
|
3
3
|
import type { DeweeChatConfig } from "dewee-webchat";
|
|
4
4
|
|
|
5
|
-
type Props = Partial<DeweeChatConfig> & { class?: string };
|
|
5
|
+
type Props = Omit<Partial<DeweeChatConfig>, "contextProvider"> & { class?: string };
|
|
6
6
|
const { class: className, ...config } = Astro.props;
|
|
7
7
|
const attrs: Record<string, string> = {};
|
|
8
8
|
for (const [key, value] of Object.entries(config)) {
|
|
9
|
-
if (value === undefined || value === null) continue;
|
|
9
|
+
if (value === undefined || value === null || typeof value === "function") continue;
|
|
10
10
|
const name = key.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
|
|
11
11
|
attrs[name] = Array.isArray(value) ? JSON.stringify(value) : String(value);
|
|
12
12
|
}
|