dewee-webchat 0.5.0-beta.3 → 0.5.0-beta.4
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 +24 -1
- package/dist/{chunk-N7BJKIPT.js → chunk-4EASDTWO.js} +1 -1
- package/dist/{chunk-Q5RDSRZG.js → chunk-OMRLBPEM.js} +1 -1
- package/dist/{handler-BchS1kD4.d.ts → handler-_GEhpm1W.d.ts} +8 -0
- package/dist/index.js +2 -2
- package/dist/react.js +2 -2
- package/dist/server-identity.d.ts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +51 -10
- package/dist/vue.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -217,7 +217,30 @@ Only exact same-origin paths in both lists use the binding. This reads the Asset
|
|
|
217
217
|
**bypassing Worker middleware, Cloudflare Access, and cookie-based authorization**. Every file under
|
|
218
218
|
the configured prefixes must be public to any valid chat visitor; keep private files elsewhere,
|
|
219
219
|
inspect the build output, and exclude sensitive files using `.assetsignore`. The URL query is not
|
|
220
|
-
passed to the binding.
|
|
220
|
+
passed to the binding.
|
|
221
|
+
|
|
222
|
+
For a sibling hostname in the same zone, a host can instead configure a service binding to a Worker
|
|
223
|
+
that serves public images from the listed directories:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
createDeweeHandler({
|
|
227
|
+
// ... other handler options
|
|
228
|
+
imageSources: ["https://images.example.com/products/"],
|
|
229
|
+
cloudflarePublicImageServices: [
|
|
230
|
+
{ id: "catalog-v1", binding: env.PUBLIC_IMAGES, prefixes: ["https://images.example.com/products/"] },
|
|
231
|
+
],
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Both lists must allow the source. Each service needs a unique ID and non-overlapping prefixes; change
|
|
236
|
+
the ID whenever you repoint the binding so existing short-lived tickets cannot reach the new service.
|
|
237
|
+
The service ID is visible in the image ticket, but the source URL remains encrypted. The binding
|
|
238
|
+
calls the configured Worker directly, bypassing the
|
|
239
|
+
source hostname's Cloudflare security rules. Every configured prefix must be public to all chat
|
|
240
|
+
visitors; do not bind private services or paths. The Worker receives a GET with the approved path on
|
|
241
|
+
`https://public-images.invalid`, without the original query, cookies or authorization headers. It
|
|
242
|
+
must serve that path without relying on its hostname or a signed query string. A missing or invalid
|
|
243
|
+
response fails closed and never falls back to a network request.
|
|
221
244
|
|
|
222
245
|
Files are limited to 2 MiB and 4096
|
|
223
246
|
pixels per edge, and responses are not cached. Animated GIF/PNG/WebP are refused. Raw HTML,
|
|
@@ -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.
|
|
24
|
+
var VERSION = "0.5.0-beta.4";
|
|
25
25
|
|
|
26
26
|
// src/actions/page-block.ts
|
|
27
27
|
var ACTION_NAME_RE = /^[a-z][a-z0-9_.-]{0,63}$/;
|
|
@@ -301,6 +301,14 @@ interface DeweeHandlerOptions {
|
|
|
301
301
|
};
|
|
302
302
|
prefixes: string[];
|
|
303
303
|
};
|
|
304
|
+
/** Public-only service bindings for approved HTTPS image directory prefixes. Bypasses host security rules. */
|
|
305
|
+
cloudflarePublicImageServices?: Array<{
|
|
306
|
+
id: string;
|
|
307
|
+
binding: {
|
|
308
|
+
fetch(request: Request): Promise<Response>;
|
|
309
|
+
};
|
|
310
|
+
prefixes: string[];
|
|
311
|
+
}>;
|
|
304
312
|
/**
|
|
305
313
|
* Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
|
|
306
314
|
* Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
|
package/dist/index.js
CHANGED
package/dist/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineDeweeChat
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-OMRLBPEM.js";
|
|
4
|
+
import "./chunk-4EASDTWO.js";
|
|
5
5
|
|
|
6
6
|
// src/react.ts
|
|
7
7
|
import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
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-
|
|
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-_GEhpm1W.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-DjEdC6QU.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-DjEdC6QU.js';
|
package/dist/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
parseJobAccessLaunch,
|
|
16
16
|
serializePageContext,
|
|
17
17
|
stripPageBlock
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4EASDTWO.js";
|
|
19
19
|
|
|
20
20
|
// src/server/admission-errors.ts
|
|
21
21
|
var MESSAGES = {
|
|
@@ -1370,7 +1370,7 @@ async function verifyImageTicket(secret, namespace, ticket) {
|
|
|
1370
1370
|
return null;
|
|
1371
1371
|
}
|
|
1372
1372
|
}
|
|
1373
|
-
function allowedImageSource(raw, prefixes, endpoint, cloudflare, publicAssets = []) {
|
|
1373
|
+
function allowedImageSource(raw, prefixes, endpoint, cloudflare, publicAssets = [], publicServices = []) {
|
|
1374
1374
|
try {
|
|
1375
1375
|
if (!raw || raw.length > 4096 || /[\u0000-\u0020\u007f\\]/.test(raw)) return null;
|
|
1376
1376
|
const match = /^https:\/\/[^/?#]+([^?#]*)/i.exec(raw);
|
|
@@ -1384,7 +1384,8 @@ function allowedImageSource(raw, prefixes, endpoint, cloudflare, publicAssets =
|
|
|
1384
1384
|
const suffix = endpoint.hostname.split(".").slice(-2).join(".");
|
|
1385
1385
|
const sameZone = source.hostname === suffix || source.hostname.endsWith(`.${suffix}`);
|
|
1386
1386
|
const isPublicAsset = source.origin === endpoint.origin && publicAssets.some((prefix) => source.pathname.startsWith(prefix));
|
|
1387
|
-
|
|
1387
|
+
const isPublicService = publicServices.some((prefix) => prefix.origin === source.origin && source.pathname.startsWith(prefix.pathname));
|
|
1388
|
+
if (sameZone && !isPublicAsset && !isPublicService) return null;
|
|
1388
1389
|
}
|
|
1389
1390
|
return prefixes.some((prefix) => prefix.origin === source.origin && source.pathname.startsWith(prefix.pathname)) ? source : null;
|
|
1390
1391
|
} catch {
|
|
@@ -1739,6 +1740,35 @@ function createDeweeHandler(options) {
|
|
|
1739
1740
|
throw new Error("[dewee-webchat] Public image asset prefixes must be explicit absolute directory paths");
|
|
1740
1741
|
}
|
|
1741
1742
|
const assetPrefixes = publicAssets?.prefixes ?? [];
|
|
1743
|
+
if (o.cloudflarePublicImageServices !== void 0 && (!Array.isArray(o.cloudflarePublicImageServices) || o.cloudflarePublicImageServices.length > 16)) {
|
|
1744
|
+
throw new Error("[dewee-webchat] Public image services must be an array");
|
|
1745
|
+
}
|
|
1746
|
+
const publicServices = (o.cloudflarePublicImageServices ?? []).map((service) => {
|
|
1747
|
+
if (!service || typeof service.id !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(service.id) || typeof service.binding?.fetch !== "function" || !Array.isArray(service.prefixes) || !service.prefixes.length) {
|
|
1748
|
+
throw new Error("[dewee-webchat] Public image services require a binding and directory prefixes");
|
|
1749
|
+
}
|
|
1750
|
+
const prefixes = service.prefixes.map((raw) => {
|
|
1751
|
+
let url;
|
|
1752
|
+
if (typeof raw !== "string" || !/^https:\/\/[^/?#]+\/(?:[A-Za-z0-9_-]+\/)+$/.test(raw)) {
|
|
1753
|
+
throw new Error("[dewee-webchat] Invalid public image service prefix");
|
|
1754
|
+
}
|
|
1755
|
+
try {
|
|
1756
|
+
url = new URL(raw);
|
|
1757
|
+
} catch {
|
|
1758
|
+
throw new Error("[dewee-webchat] Invalid public image service prefix");
|
|
1759
|
+
}
|
|
1760
|
+
if (url.protocol !== "https:" || url.username || url.password || url.port || url.search || url.hash || !/^\/(?:[A-Za-z0-9_-]+\/)+$/.test(url.pathname)) {
|
|
1761
|
+
throw new Error("[dewee-webchat] Public image service prefixes must be HTTPS origins with explicit directory paths");
|
|
1762
|
+
}
|
|
1763
|
+
return url;
|
|
1764
|
+
});
|
|
1765
|
+
return { id: service.id, binding: service.binding, prefixes };
|
|
1766
|
+
});
|
|
1767
|
+
const servicePrefixes = publicServices.flatMap((service) => service.prefixes);
|
|
1768
|
+
if (new Set(publicServices.map((service) => service.id)).size !== publicServices.length || servicePrefixes.some((prefix, index) => servicePrefixes.slice(index + 1).some((other) => prefix.origin === other.origin && (prefix.pathname.startsWith(other.pathname) || other.pathname.startsWith(prefix.pathname))))) {
|
|
1769
|
+
throw new Error("[dewee-webchat] Public image service ids and directory prefixes must not overlap");
|
|
1770
|
+
}
|
|
1771
|
+
const serviceFor = (source) => publicServices.find((service) => service.prefixes.some((prefix) => prefix.origin === source.origin && source.pathname.startsWith(prefix.pathname)));
|
|
1742
1772
|
let activeImages = 0;
|
|
1743
1773
|
const imageNamespace = `${o.ccp?.chatUrl ?? o.gatewayUrl ?? ""}
|
|
1744
1774
|
${o.ccp?.integrationId ?? o.tenantId ?? ""}`;
|
|
@@ -2661,32 +2691,43 @@ ${o.ccp?.integrationId ?? o.tenantId ?? ""}`;
|
|
|
2661
2691
|
}
|
|
2662
2692
|
const raw = parsed?.url;
|
|
2663
2693
|
const worker = globalThis.navigator?.userAgent === "Cloudflare-Workers";
|
|
2664
|
-
const source = typeof raw === "string" ? allowedImageSource(raw, imageSources, new URL(req.url), worker, worker ? assetPrefixes : []) : null;
|
|
2694
|
+
const source = typeof raw === "string" ? allowedImageSource(raw, imageSources, new URL(req.url), worker, worker ? assetPrefixes : [], worker ? servicePrefixes : []) : null;
|
|
2665
2695
|
if (!source) return fail(req, 403, "Image not allowed", "forbidden_image");
|
|
2666
2696
|
const endpoint = new URL(req.url);
|
|
2667
2697
|
const asset = publicAssets && source.origin === endpoint.origin && assetPrefixes.some((prefix) => source.pathname.startsWith(prefix));
|
|
2698
|
+
const service = asset ? void 0 : serviceFor(source);
|
|
2668
2699
|
const namespace = asset ? `${imageNamespace}
|
|
2669
|
-
assets:${endpoint.origin}` : imageNamespace
|
|
2670
|
-
|
|
2700
|
+
assets:${endpoint.origin}` : service ? `${imageNamespace}
|
|
2701
|
+
service:${endpoint.origin}:${service.id}` : imageNamespace;
|
|
2702
|
+
const ticket = await issueImageTicket(secret, namespace, claims.v, source.href);
|
|
2703
|
+
return json(req, { ticket: service ? `s.${service.id}.${ticket}` : ticket });
|
|
2671
2704
|
}
|
|
2672
2705
|
if (req.method === "GET" && action === "image" && imageSources.length) {
|
|
2673
2706
|
const ticket = new URL(req.url).searchParams.get("ticket") ?? "";
|
|
2674
2707
|
const endpoint = new URL(req.url);
|
|
2675
|
-
const
|
|
2708
|
+
const tagged = /^s\.([A-Za-z0-9_-]{1,64})\.([A-Za-z0-9_-]{1,8192})$/.exec(ticket);
|
|
2709
|
+
const serviceId = tagged?.[1];
|
|
2710
|
+
if (serviceId && !publicServices.some((service2) => service2.id === serviceId)) return fail(req, 401, "Image ticket expired", "unauthorized");
|
|
2711
|
+
const serviceClaims = tagged ? await verifyImageTicket(secret, `${imageNamespace}
|
|
2712
|
+
service:${endpoint.origin}:${serviceId}`, tagged[2]) : null;
|
|
2713
|
+
const assetClaims = tagged ? null : await verifyImageTicket(secret, `${imageNamespace}
|
|
2676
2714
|
assets:${endpoint.origin}`, ticket);
|
|
2677
|
-
const claims = assetClaims ?? await verifyImageTicket(secret, imageNamespace, ticket);
|
|
2715
|
+
const claims = tagged ? serviceClaims : assetClaims ?? await verifyImageTicket(secret, imageNamespace, ticket);
|
|
2678
2716
|
if (!claims) return fail(req, 401, "Image ticket expired", "unauthorized");
|
|
2679
2717
|
const worker = globalThis.navigator?.userAgent === "Cloudflare-Workers";
|
|
2680
|
-
const source = allowedImageSource(claims.source, imageSources, endpoint, worker, worker ? assetPrefixes : []);
|
|
2718
|
+
const source = allowedImageSource(claims.source, imageSources, endpoint, worker, worker ? assetPrefixes : [], worker ? servicePrefixes : []);
|
|
2681
2719
|
if (!source) return fail(req, 403, "Image not allowed", "forbidden_image");
|
|
2682
2720
|
const asset = publicAssets && source.origin === endpoint.origin && assetPrefixes.some((prefix) => source.pathname.startsWith(prefix));
|
|
2683
2721
|
if (Boolean(asset) !== Boolean(assetClaims)) return fail(req, 403, "Image not allowed", "forbidden_image");
|
|
2722
|
+
const service = asset ? void 0 : serviceFor(source);
|
|
2723
|
+
if (service?.id !== serviceId) return fail(req, 403, "Image not allowed", "forbidden_image");
|
|
2684
2724
|
if (limiter && !await limiter(`image:${claims.visitor}`, "preauth")) return fail(req, 429, "Too many requests, try again later", "rate_limited");
|
|
2685
2725
|
if (activeImages >= 8) return fail(req, 429, "Too many requests, try again later", "rate_limited");
|
|
2686
2726
|
activeImages++;
|
|
2687
2727
|
try {
|
|
2688
2728
|
const assetFetch = (input, init) => publicAssets.binding.fetch(new Request(new URL(new URL(String(input)).pathname, "https://assets.invalid"), init));
|
|
2689
|
-
const
|
|
2729
|
+
const serviceFetch = (input, init) => service.binding.fetch(new Request(new URL(new URL(String(input)).pathname, "https://public-images.invalid"), init));
|
|
2730
|
+
const result = await fetchRasterImage(source.href, asset ? assetFetch : service ? serviceFetch : worker ? globalThis.fetch : publicImageFetchNode, worker || service ? "manual" : "error");
|
|
2690
2731
|
return result ?? fail(req, 502, "Image unavailable", "image_unavailable");
|
|
2691
2732
|
} finally {
|
|
2692
2733
|
activeImages--;
|
package/dist/vue.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dewee-webchat",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.4",
|
|
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",
|