dewee-webchat 0.5.0-beta.1 → 0.5.0-beta.2

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 CHANGED
@@ -202,8 +202,24 @@ The script-tag equivalent is `data-image-origins`; vanilla JS uses
202
202
  redirects, and accepts PNG, JPEG, GIF and WebP only after validating the content type and file
203
203
  signature. Node and PHP pin a checked public DNS address; Cloudflare Workers use the platform's
204
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
205
+ labels (including sibling subdomains), to avoid bypassing Cloudflare's own-zone protections. For
206
+ same-origin static files in the Worker's own Assets binding, a host can explicitly opt in:
207
+
208
+ ```ts
209
+ createDeweeHandler({
210
+ // ... other handler options
211
+ imageSources: ["https://chat.example.com/public-chat-images/"],
212
+ cloudflarePublicImageAssets: { binding: env.ASSETS, prefixes: ["/public-chat-images/"] },
213
+ });
214
+ ```
215
+
216
+ Only exact same-origin paths in both lists use the binding. This reads the Assets collection directly,
217
+ **bypassing Worker middleware, Cloudflare Access, and cookie-based authorization**. Every file under
218
+ the configured prefixes must be public to any valid chat visitor; keep private files elsewhere,
219
+ inspect the build output, and exclude sensitive files using `.assetsignore`. The URL query is not
220
+ passed to the binding. Sibling domains still require a separate approved image domain.
221
+
222
+ Files are limited to 2 MiB and 4096
207
223
  pixels per edge, and responses are not cached. Animated GIF/PNG/WebP are refused. Raw HTML,
208
224
  SVG, unsafe schemes, credentials in URLs and unapproved origins are rejected.
209
225
 
@@ -21,7 +21,7 @@ function isLinkErrorCode(code) {
21
21
  }
22
22
  var HEADER_GATEWAY_URL = "x-dewee-gateway-url";
23
23
  var HEADER_GATEWAY_TOKEN = "x-dewee-gateway-token";
24
- var VERSION = "0.5.0-beta.1";
24
+ var VERSION = "0.5.0-beta.2";
25
25
 
26
26
  // src/actions/page-block.ts
27
27
  var ACTION_NAME_RE = /^[a-z][a-z0-9_.-]{0,63}$/;
@@ -9,7 +9,7 @@ import {
9
9
  isLinkErrorCode,
10
10
  parseJobAccessLaunch,
11
11
  stripPageBlock
12
- } from "./chunk-WHHQ6FKK.js";
12
+ } from "./chunk-KXW4EWXA.js";
13
13
 
14
14
  // src/actions/directives.ts
15
15
  var DIRECTIVE_LANG = "dewee-action";
@@ -290,6 +290,13 @@ interface DeweeHandlerOptions {
290
290
  allowedHeaders?: string[];
291
291
  /** HTTPS directory prefixes from which the backend may proxy images; disabled when unset. */
292
292
  imageSources?: string[];
293
+ /** Explicitly public directories in this Worker's Assets binding. Bypasses site middleware. */
294
+ cloudflarePublicImageAssets?: {
295
+ binding: {
296
+ fetch(request: Request): Promise<Response>;
297
+ };
298
+ prefixes: string[];
299
+ };
293
300
  /**
294
301
  * Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
295
302
  * Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  defineDeweeChat,
8
8
  mount,
9
9
  renderMarkdown
10
- } from "./chunk-M6J3IXEI.js";
11
- import "./chunk-WHHQ6FKK.js";
10
+ } from "./chunk-XCO4TCKF.js";
11
+ import "./chunk-KXW4EWXA.js";
12
12
  export {
13
13
  ApiClient,
14
14
  ApiError,
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineDeweeChat
3
- } from "./chunk-M6J3IXEI.js";
4
- import "./chunk-WHHQ6FKK.js";
3
+ } from "./chunk-XCO4TCKF.js";
4
+ import "./chunk-KXW4EWXA.js";
5
5
 
6
6
  // src/react.ts
7
7
  import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
@@ -1,4 +1,4 @@
1
- import { D as DeweeIdentitySdk } from './handler-B-c6tDr7.js';
1
+ import { D as DeweeIdentitySdk } from './handler-SsDyK0TG.js';
2
2
  import './protocol-BbjGSfZa.js';
3
3
 
4
4
  declare const deweeIdentitySdk: DeweeIdentitySdk;
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySessionStore, g as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket, h as RateLimiter, S as StoredIdentitySession, V as VisitorClaims, i as createDeweeHandler, m as memoryIdentitySessionStore, j as memoryRateLimiter, o as optionsFromEnv, s as signVisitorToken, v as verifyVisitorToken } from './handler-B-c6tDr7.js';
1
+ export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySessionStore, g as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket, h as RateLimiter, S as StoredIdentitySession, V as VisitorClaims, i as createDeweeHandler, m as memoryIdentitySessionStore, j as memoryRateLimiter, o as optionsFromEnv, s as signVisitorToken, v as verifyVisitorToken } from './handler-SsDyK0TG.js';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
3
  import { R as Remediation, H as HistoryMessage, A as AgentInfo, C as ChatStreamEvent, a as ActionManifestEntry, b as AdmissionErrorCode } from './protocol-BbjGSfZa.js';
4
4
  export { c as ADMISSION_ERROR_CODES, d as ActionSensitivity, e as ChatRequestBody, E as ErrorBody, I as IdentityErrorCode, L as LinkResponse, f as LinkTarget, P as PublicConfig, S as SessionResponse, i as isAdmissionErrorCode } from './protocol-BbjGSfZa.js';
package/dist/server.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  parseJobAccessLaunch,
14
14
  serializePageContext,
15
15
  stripPageBlock
16
- } from "./chunk-WHHQ6FKK.js";
16
+ } from "./chunk-KXW4EWXA.js";
17
17
 
18
18
  // src/server/admission-errors.ts
19
19
  var MESSAGES = {
@@ -1325,7 +1325,7 @@ async function verifyImageTicket(secret, namespace, ticket) {
1325
1325
  return null;
1326
1326
  }
1327
1327
  }
1328
- function allowedImageSource(raw, prefixes, endpoint, cloudflare) {
1328
+ function allowedImageSource(raw, prefixes, endpoint, cloudflare, publicAssets = []) {
1329
1329
  try {
1330
1330
  if (!raw || raw.length > 4096 || /[\u0000-\u0020\u007f\\]/.test(raw)) return null;
1331
1331
  const match = /^https:\/\/[^/?#]+([^?#]*)/i.exec(raw);
@@ -1337,7 +1337,9 @@ function allowedImageSource(raw, prefixes, endpoint, cloudflare) {
1337
1337
  if (!parent && /^\/(?:image|image-ticket)(?:\/|$)/.test(source.pathname)) return null;
1338
1338
  if (cloudflare) {
1339
1339
  const suffix = endpoint.hostname.split(".").slice(-2).join(".");
1340
- if (source.hostname === suffix || source.hostname.endsWith(`.${suffix}`)) return null;
1340
+ const sameZone = source.hostname === suffix || source.hostname.endsWith(`.${suffix}`);
1341
+ const isPublicAsset = source.origin === endpoint.origin && publicAssets.some((prefix) => source.pathname.startsWith(prefix));
1342
+ if (sameZone && !isPublicAsset) return null;
1341
1343
  }
1342
1344
  return prefixes.some((prefix) => prefix.origin === source.origin && source.pathname.startsWith(prefix.pathname)) ? source : null;
1343
1345
  } catch {
@@ -1675,6 +1677,11 @@ function createDeweeHandler(options) {
1675
1677
  if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.port || !url.pathname.endsWith("/") || /%(?:2f|5c|25|2e|0[0-9a-f]|1[0-9a-f]|7f)/i.test(url.pathname)) throw new Error("[dewee-webchat] Image sources must be HTTPS directory prefixes on the default port");
1676
1678
  return url;
1677
1679
  });
1680
+ const publicAssets = o.cloudflarePublicImageAssets;
1681
+ if (publicAssets && (typeof publicAssets.binding?.fetch !== "function" || !Array.isArray(publicAssets.prefixes) || !publicAssets.prefixes.length || publicAssets.prefixes.some((path) => typeof path !== "string" || !/^\/(?:[A-Za-z0-9_-]+\/)+$/.test(path)))) {
1682
+ throw new Error("[dewee-webchat] Public image asset prefixes must be explicit absolute directory paths");
1683
+ }
1684
+ const assetPrefixes = publicAssets?.prefixes ?? [];
1678
1685
  let activeImages = 0;
1679
1686
  const imageNamespace = `${o.ccp?.chatUrl ?? o.gatewayUrl ?? ""}
1680
1687
  ${o.ccp?.integrationId ?? o.tenantId ?? ""}`;
@@ -2545,22 +2552,32 @@ ${o.ccp?.integrationId ?? o.tenantId ?? ""}`;
2545
2552
  }
2546
2553
  const raw = parsed?.url;
2547
2554
  const worker = globalThis.navigator?.userAgent === "Cloudflare-Workers";
2548
- const source = typeof raw === "string" ? allowedImageSource(raw, imageSources, new URL(req.url), worker) : null;
2555
+ const source = typeof raw === "string" ? allowedImageSource(raw, imageSources, new URL(req.url), worker, worker ? assetPrefixes : []) : null;
2549
2556
  if (!source) return fail(req, 403, "Image not allowed", "forbidden_image");
2550
- return json(req, { ticket: await issueImageTicket(secret, imageNamespace, claims.v, source.href) });
2557
+ const endpoint = new URL(req.url);
2558
+ const asset = publicAssets && source.origin === endpoint.origin && assetPrefixes.some((prefix) => source.pathname.startsWith(prefix));
2559
+ const namespace = asset ? `${imageNamespace}
2560
+ assets:${endpoint.origin}` : imageNamespace;
2561
+ return json(req, { ticket: await issueImageTicket(secret, namespace, claims.v, source.href) });
2551
2562
  }
2552
2563
  if (req.method === "GET" && action === "image" && imageSources.length) {
2553
2564
  const ticket = new URL(req.url).searchParams.get("ticket") ?? "";
2554
- const claims = await verifyImageTicket(secret, imageNamespace, ticket);
2565
+ const endpoint = new URL(req.url);
2566
+ const assetClaims = await verifyImageTicket(secret, `${imageNamespace}
2567
+ assets:${endpoint.origin}`, ticket);
2568
+ const claims = assetClaims ?? await verifyImageTicket(secret, imageNamespace, ticket);
2555
2569
  if (!claims) return fail(req, 401, "Image ticket expired", "unauthorized");
2556
2570
  const worker = globalThis.navigator?.userAgent === "Cloudflare-Workers";
2557
- const source = allowedImageSource(claims.source, imageSources, new URL(req.url), worker);
2571
+ const source = allowedImageSource(claims.source, imageSources, endpoint, worker, worker ? assetPrefixes : []);
2558
2572
  if (!source) return fail(req, 403, "Image not allowed", "forbidden_image");
2573
+ const asset = publicAssets && source.origin === endpoint.origin && assetPrefixes.some((prefix) => source.pathname.startsWith(prefix));
2574
+ if (Boolean(asset) !== Boolean(assetClaims)) return fail(req, 403, "Image not allowed", "forbidden_image");
2559
2575
  if (limiter && !await limiter(`image:${claims.visitor}`, "preauth")) return fail(req, 429, "Too many requests, try again later", "rate_limited");
2560
2576
  if (activeImages >= 8) return fail(req, 429, "Too many requests, try again later", "rate_limited");
2561
2577
  activeImages++;
2562
2578
  try {
2563
- const result = await fetchRasterImage(source.href, worker ? globalThis.fetch : publicImageFetchNode, worker ? "manual" : "error");
2579
+ const assetFetch = (input, init) => publicAssets.binding.fetch(new Request(new URL(new URL(String(input)).pathname, "https://assets.invalid"), init));
2580
+ const result = await fetchRasterImage(source.href, asset ? assetFetch : worker ? globalThis.fetch : publicImageFetchNode, worker ? "manual" : "error");
2564
2581
  return result ?? fail(req, 502, "Image unavailable", "image_unavailable");
2565
2582
  } finally {
2566
2583
  activeImages--;
package/dist/vue.d.ts CHANGED
@@ -40,26 +40,6 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
40
40
  type: PropType<any>;
41
41
  default: undefined;
42
42
  };
43
- imageOrigins: {
44
- type: PropType<any>;
45
- default: undefined;
46
- };
47
- pageContext: {
48
- type: PropType<any>;
49
- default: undefined;
50
- };
51
- contextSelector: {
52
- type: PropType<any>;
53
- default: undefined;
54
- };
55
- excludeSelectors: {
56
- type: PropType<any>;
57
- default: undefined;
58
- };
59
- contextProvider: {
60
- type: PropType<any>;
61
- default: undefined;
62
- };
63
43
  launcherAvatar: {
64
44
  type: PropType<any>;
65
45
  default: undefined;
@@ -148,78 +128,78 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
148
128
  type: PropType<any>;
149
129
  default: undefined;
150
130
  };
151
- domActions: {
131
+ headers: {
152
132
  type: PropType<any>;
153
133
  default: undefined;
154
134
  };
155
- domActionsRoot: {
135
+ fetch: {
156
136
  type: PropType<any>;
157
137
  default: undefined;
158
138
  };
159
- headers: {
139
+ domActions: {
160
140
  type: PropType<any>;
161
141
  default: undefined;
162
142
  };
163
- fetch: {
143
+ domActionsRoot: {
164
144
  type: PropType<any>;
165
145
  default: undefined;
166
146
  };
167
- }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
168
- [key: string]: any;
169
- }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("open" | "close" | "error" | "send" | "action" | "ready" | "reply" | "context-ready" | "context-error")[], "open" | "close" | "error" | "send" | "action" | "ready" | "reply" | "context-ready" | "context-error", vue.PublicProps, Readonly<vue.ExtractPropTypes<{
170
- endpoint: {
147
+ imageOrigins: {
171
148
  type: PropType<any>;
172
149
  default: undefined;
173
150
  };
174
- agent: {
151
+ pageContext: {
175
152
  type: PropType<any>;
176
153
  default: undefined;
177
154
  };
178
- title: {
155
+ contextSelector: {
179
156
  type: PropType<any>;
180
157
  default: undefined;
181
158
  };
182
- subtitle: {
159
+ excludeSelectors: {
183
160
  type: PropType<any>;
184
161
  default: undefined;
185
162
  };
186
- greeting: {
163
+ contextProvider: {
187
164
  type: PropType<any>;
188
165
  default: undefined;
189
166
  };
190
- intro: {
167
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
168
+ [key: string]: any;
169
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("open" | "ready" | "send" | "reply" | "error" | "close" | "action" | "context-ready" | "context-error")[], "open" | "ready" | "send" | "reply" | "error" | "close" | "action" | "context-ready" | "context-error", vue.PublicProps, Readonly<vue.ExtractPropTypes<{
170
+ endpoint: {
191
171
  type: PropType<any>;
192
172
  default: undefined;
193
173
  };
194
- placeholder: {
174
+ agent: {
195
175
  type: PropType<any>;
196
176
  default: undefined;
197
177
  };
198
- suggestions: {
178
+ title: {
199
179
  type: PropType<any>;
200
180
  default: undefined;
201
181
  };
202
- avatar: {
182
+ subtitle: {
203
183
  type: PropType<any>;
204
184
  default: undefined;
205
185
  };
206
- imageOrigins: {
186
+ greeting: {
207
187
  type: PropType<any>;
208
188
  default: undefined;
209
189
  };
210
- pageContext: {
190
+ intro: {
211
191
  type: PropType<any>;
212
192
  default: undefined;
213
193
  };
214
- contextSelector: {
194
+ placeholder: {
215
195
  type: PropType<any>;
216
196
  default: undefined;
217
197
  };
218
- excludeSelectors: {
198
+ suggestions: {
219
199
  type: PropType<any>;
220
200
  default: undefined;
221
201
  };
222
- contextProvider: {
202
+ avatar: {
223
203
  type: PropType<any>;
224
204
  default: undefined;
225
205
  };
@@ -311,6 +291,14 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
311
291
  type: PropType<any>;
312
292
  default: undefined;
313
293
  };
294
+ headers: {
295
+ type: PropType<any>;
296
+ default: undefined;
297
+ };
298
+ fetch: {
299
+ type: PropType<any>;
300
+ default: undefined;
301
+ };
314
302
  domActions: {
315
303
  type: PropType<any>;
316
304
  default: undefined;
@@ -319,20 +307,32 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
319
307
  type: PropType<any>;
320
308
  default: undefined;
321
309
  };
322
- headers: {
310
+ imageOrigins: {
323
311
  type: PropType<any>;
324
312
  default: undefined;
325
313
  };
326
- fetch: {
314
+ pageContext: {
315
+ type: PropType<any>;
316
+ default: undefined;
317
+ };
318
+ contextSelector: {
319
+ type: PropType<any>;
320
+ default: undefined;
321
+ };
322
+ excludeSelectors: {
323
+ type: PropType<any>;
324
+ default: undefined;
325
+ };
326
+ contextProvider: {
327
327
  type: PropType<any>;
328
328
  default: undefined;
329
329
  };
330
330
  }>> & Readonly<{
331
+ onOpen?: ((...args: any[]) => any) | undefined;
331
332
  onReady?: ((...args: any[]) => any) | undefined;
332
333
  onSend?: ((...args: any[]) => any) | undefined;
333
334
  onReply?: ((...args: any[]) => any) | undefined;
334
335
  onError?: ((...args: any[]) => any) | undefined;
335
- onOpen?: ((...args: any[]) => any) | undefined;
336
336
  onClose?: ((...args: any[]) => any) | undefined;
337
337
  onAction?: ((...args: any[]) => any) | undefined;
338
338
  "onContext-ready"?: ((...args: any[]) => any) | undefined;
@@ -347,11 +347,6 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
347
347
  placeholder: any;
348
348
  suggestions: any;
349
349
  avatar: any;
350
- imageOrigins: any;
351
- pageContext: any;
352
- contextSelector: any;
353
- excludeSelectors: any;
354
- contextProvider: any;
355
350
  launcherAvatar: any;
356
351
  position: any;
357
352
  mode: any;
@@ -374,10 +369,15 @@ declare const DeweeChat: vue.DefineComponent<vue.ExtractPropTypes<{
374
369
  getPageContext: any;
375
370
  maxActionRounds: any;
376
371
  actionTimeoutMs: any;
377
- domActions: any;
378
- domActionsRoot: any;
379
372
  headers: any;
380
373
  fetch: any;
374
+ domActions: any;
375
+ domActionsRoot: any;
376
+ imageOrigins: any;
377
+ pageContext: any;
378
+ contextSelector: any;
379
+ excludeSelectors: any;
380
+ contextProvider: any;
381
381
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
382
382
 
383
383
  export { DeweeChat, DeweeChat as default };
package/dist/vue.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineDeweeChat
3
- } from "./chunk-M6J3IXEI.js";
4
- import "./chunk-WHHQ6FKK.js";
3
+ } from "./chunk-XCO4TCKF.js";
4
+ import "./chunk-KXW4EWXA.js";
5
5
 
6
6
  // src/vue.ts
7
7
  import { defineComponent, h, onMounted, ref, watch } from "vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dewee-webchat",
3
- "version": "0.5.0-beta.1",
3
+ "version": "0.5.0-beta.2",
4
4
  "description": "Embeddable chat widget for Dewee AI agents — web component, React/Vue/Astro wrappers and a secure backend handler (Workers, Next.js, Node, Bun, Deno).",
5
5
  "license": "MIT",
6
6
  "type": "module",