veryfront 0.1.124 → 0.1.127
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/esm/deno.js +1 -1
- package/esm/src/html/hydration-script-builder/templates/router.d.ts.map +1 -1
- package/esm/src/html/hydration-script-builder/templates/router.js +9 -0
- package/esm/src/react/components/Head.d.ts.map +1 -1
- package/esm/src/react/components/Head.js +5 -0
- package/esm/src/react/components/ai/chat/composition/chat-root.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/composition/chat-root.js +3 -1
- package/esm/src/react/components/ai/chat-with-sidebar.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat-with-sidebar.js +3 -1
- package/esm/src/react/components/ai/csp-nonce.d.ts +6 -0
- package/esm/src/react/components/ai/csp-nonce.d.ts.map +1 -0
- package/esm/src/react/components/ai/csp-nonce.js +13 -0
- package/esm/src/security/http/response/security-handler.d.ts.map +1 -1
- package/esm/src/security/http/response/security-handler.js +12 -4
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +186 -0
- package/esm/src/transforms/mdx/esm-module-loader/import-transformer.js +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/jsx-cache.js +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.d.ts +1 -14
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.js +50 -8
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +18 -17
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/html/hydration-script-builder/templates/router.ts +9 -0
- package/src/src/react/components/Head.tsx +5 -0
- package/src/src/react/components/ai/chat/composition/chat-root.tsx +3 -1
- package/src/src/react/components/ai/chat-with-sidebar.tsx +3 -1
- package/src/src/react/components/ai/csp-nonce.ts +13 -0
- package/src/src/security/http/response/security-handler.ts +12 -4
- package/src/src/server/handlers/dev/framework-candidates.generated.ts +186 -0
- package/src/src/transforms/mdx/esm-module-loader/import-transformer.ts +1 -1
- package/src/src/transforms/mdx/esm-module-loader/jsx-cache.ts +1 -1
- package/src/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.ts +54 -12
- package/src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts +1 -1
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +17 -13
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -143,7 +143,7 @@ export async function transformJsxImports(code, adapter, esmCacheDir) {
|
|
|
143
143
|
// Rewrite _dnt.polyfills.js / _dnt.shims.js relative imports to absolute file:// paths.
|
|
144
144
|
// Framework files from the npm package contain relative dnt imports that resolve
|
|
145
145
|
// incorrectly when cached to a different directory.
|
|
146
|
-
transformed = rewriteDntImports(transformed, filePath);
|
|
146
|
+
transformed = await rewriteDntImports(transformed, filePath);
|
|
147
147
|
await getLocalFs().writeTextFile(transformedPath, transformed);
|
|
148
148
|
return {
|
|
149
149
|
original: fullMatch,
|
|
@@ -17,7 +17,7 @@ export async function ensureCachedJsxModulePatched(transformedPath, sourceFilePa
|
|
|
17
17
|
const fs = getLocalFs();
|
|
18
18
|
try {
|
|
19
19
|
const cachedCode = await fs.readTextFile(transformedPath);
|
|
20
|
-
const rewritten = rewriteDntImports(cachedCode, sourceFilePath);
|
|
20
|
+
const rewritten = await rewriteDntImports(cachedCode, sourceFilePath);
|
|
21
21
|
if (rewritten === cachedCode)
|
|
22
22
|
return true;
|
|
23
23
|
await fs.writeTextFile(transformedPath, rewritten);
|
|
@@ -8,18 +8,5 @@
|
|
|
8
8
|
* Uses deno.json exports/imports as the source of truth and appends ?ssr=true.
|
|
9
9
|
*/
|
|
10
10
|
export declare function rewriteVeryfrontImports(code: string): string;
|
|
11
|
-
|
|
12
|
-
* Rewrite relative imports in framework files to absolute file:// paths.
|
|
13
|
-
*
|
|
14
|
-
* Framework files from the npm package (e.g., Head.js) contain relative imports like:
|
|
15
|
-
* import "../../../_dnt.polyfills.js"
|
|
16
|
-
* import { collectHead } from "../head-collector.js"
|
|
17
|
-
*
|
|
18
|
-
* These resolve correctly when loaded from the npm package directory, but break when
|
|
19
|
-
* the transformed code is cached to a different directory (e.g., /app/.cache/veryfront-mdx-esm/...).
|
|
20
|
-
* The relative path would resolve to /app/.cache/head-collector.js which doesn't exist.
|
|
21
|
-
*
|
|
22
|
-
* Fix: Replace ALL relative imports with absolute file:// paths resolved from the source file's directory.
|
|
23
|
-
*/
|
|
24
|
-
export declare function rewriteDntImports(code: string, sourceFilePath: string): string;
|
|
11
|
+
export declare function rewriteDntImports(code: string, sourceFilePath: string): Promise<string>;
|
|
25
12
|
//# sourceMappingURL=import-rewriter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/import-rewriter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5D;AAwCD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8C7F"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { dirname, join, resolve } from "../../../../platform/compat/path/index.js";
|
|
7
7
|
import { FRAMEWORK_ROOT } from "../constants.js";
|
|
8
8
|
import { resolveVeryfrontModuleUrl } from "../../../veryfront-module-urls.js";
|
|
9
|
+
import { getLocalFs } from "../cache/index.js";
|
|
9
10
|
/**
|
|
10
11
|
* Rewrite veryfront/* imports to /_vf_modules/_veryfront/ paths for MDX module loading.
|
|
11
12
|
* Uses deno.json exports/imports as the source of truth and appends ?ssr=true.
|
|
@@ -31,7 +32,27 @@ export function rewriteVeryfrontImports(code) {
|
|
|
31
32
|
*
|
|
32
33
|
* Fix: Replace ALL relative imports with absolute file:// paths resolved from the source file's directory.
|
|
33
34
|
*/
|
|
34
|
-
|
|
35
|
+
async function findExistingFrameworkRelativeTarget(absolutePath) {
|
|
36
|
+
const fs = getLocalFs();
|
|
37
|
+
const candidates = [absolutePath, `${absolutePath}.src`];
|
|
38
|
+
if (absolutePath.endsWith(".js") || absolutePath.endsWith(".mjs")) {
|
|
39
|
+
const stem = absolutePath.replace(/\.(?:m?js)$/, "");
|
|
40
|
+
for (const ext of [".ts", ".tsx", ".jsx", ".js", ".mjs"]) {
|
|
41
|
+
candidates.push(`${stem}${ext}.src`, `${stem}${ext}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
await fs.stat(candidate);
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* expected: candidate may not exist */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export async function rewriteDntImports(code, sourceFilePath) {
|
|
35
56
|
// Only needed for framework files that come from the npm package.
|
|
36
57
|
// IMPORTANT: Use FRAMEWORK_ROOT + "src/" or dist/framework-src to avoid matching project source files
|
|
37
58
|
// that live under FRAMEWORK_ROOT (e.g., projects/myproject/components/...).
|
|
@@ -46,11 +67,32 @@ export function rewriteDntImports(code, sourceFilePath) {
|
|
|
46
67
|
return code;
|
|
47
68
|
}
|
|
48
69
|
const sourceDir = dirname(sourceFilePath);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
const needsFrameworkSourceFallback = sourceFilePath.startsWith(frameworkSrcRoot) ||
|
|
71
|
+
sourceFilePath.startsWith(embeddedSrcRoot);
|
|
72
|
+
let rewritten = code;
|
|
73
|
+
const patterns = [
|
|
74
|
+
{
|
|
75
|
+
regex: /from\s*["'](\.\.?\/[^"']+)["']/g,
|
|
76
|
+
buildReplacement: (path) => `from "file://${path}"`,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
regex: /import\s*["'](\.\.?\/[^"']+)["']/g,
|
|
80
|
+
buildReplacement: (path) => `import "file://${path}"`,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
for (const { regex, buildReplacement } of patterns) {
|
|
84
|
+
const matches = [...rewritten.matchAll(regex)];
|
|
85
|
+
for (const match of matches) {
|
|
86
|
+
const original = match[0];
|
|
87
|
+
const relativePath = match[1];
|
|
88
|
+
if (!relativePath)
|
|
89
|
+
continue;
|
|
90
|
+
const absolutePath = resolve(sourceDir, relativePath);
|
|
91
|
+
const resolvedPath = needsFrameworkSourceFallback
|
|
92
|
+
? await findExistingFrameworkRelativeTarget(absolutePath) ?? absolutePath
|
|
93
|
+
: absolutePath;
|
|
94
|
+
rewritten = rewritten.replace(original, buildReplacement(resolvedPath));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return rewritten;
|
|
56
98
|
}
|
|
@@ -244,7 +244,7 @@ async function doFetchAndCacheModule(normalizedPath, context, fetchAndCacheModul
|
|
|
244
244
|
outputLength: moduleCode.length,
|
|
245
245
|
});
|
|
246
246
|
// Rewrite _dnt.polyfills.js / _dnt.shims.js relative imports to absolute file:// paths
|
|
247
|
-
moduleCode = rewriteDntImports(moduleCode, actualFilePath);
|
|
247
|
+
moduleCode = await rewriteDntImports(moduleCode, actualFilePath);
|
|
248
248
|
// Cache HTTP imports (esm.sh URLs) to local file:// paths.
|
|
249
249
|
// This ensures the same cache works for both compiled and non-compiled Deno.
|
|
250
250
|
// Compiled binaries cannot do dynamic HTTP imports, but non-compiled Deno
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-resolver.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAU,MAAM,mCAAmC,CAAC;AAY7E,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmBzD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACvC,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA6DzD;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkExB;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACxC,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"path-resolver.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAU,MAAM,mCAAmC,CAAC;AAY7E,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmBzD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACvC,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA6DzD;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkExB;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EACxC,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAU,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqExB"}
|
|
@@ -184,26 +184,27 @@ export async function resolveRelativeFrameworkImport(specifier, fromSourcePath,
|
|
|
184
184
|
// If specifier already has extension (e.g., ./Head.tsx), we need to try:
|
|
185
185
|
// 1. The exact path (basePath)
|
|
186
186
|
// 2. The path with .src suffix (basePath.src) for embedded sources
|
|
187
|
-
// 3.
|
|
187
|
+
// 3. For transpiled .js/.mjs imports, fall back to sibling TS/TSX/JSX sources
|
|
188
188
|
if (/\.(tsx?|jsx?|mjs)$/.test(specifier)) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const srcPath = basePath + ".src";
|
|
200
|
-
if (await existsFn(srcPath))
|
|
201
|
-
return srcPath;
|
|
189
|
+
const explicitCandidates = [basePath, `${basePath}.src`];
|
|
190
|
+
// esbuild rewrites TS/TSX relative imports to .js in transformed output.
|
|
191
|
+
// When the original source only exists as .ts/.tsx (or embedded .src),
|
|
192
|
+
// probe those sibling source extensions before giving up.
|
|
193
|
+
if (basePath.endsWith(".js") || basePath.endsWith(".mjs")) {
|
|
194
|
+
const stem = basePath.replace(/\.(?:m?js)$/, "");
|
|
195
|
+
for (const ext of [".ts", ".tsx", ".jsx", ".js", ".mjs"]) {
|
|
196
|
+
explicitCandidates.push(`${stem}${ext}.src`, `${stem}${ext}`);
|
|
197
|
+
}
|
|
202
198
|
}
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
for (const candidate of explicitCandidates) {
|
|
200
|
+
try {
|
|
201
|
+
if (await existsFn(candidate))
|
|
202
|
+
return candidate;
|
|
203
|
+
}
|
|
204
|
+
catch (_) {
|
|
205
|
+
/* expected: file may not exist at this path */
|
|
206
|
+
}
|
|
205
207
|
}
|
|
206
|
-
// Not found with explicit extension
|
|
207
208
|
return null;
|
|
208
209
|
}
|
|
209
210
|
// No extension provided - try all extensions (including .src for embedded sources)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.127";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -42,6 +42,13 @@ export const getRouterScript = () => `
|
|
|
42
42
|
const log = DEBUG ? console.log.bind(console, '[Veryfront]') : () => {};
|
|
43
43
|
const logError = console.error.bind(console, '[Veryfront]');
|
|
44
44
|
|
|
45
|
+
function getDocumentNonce() {
|
|
46
|
+
const element = document.querySelector('script[nonce], style[nonce], link[nonce]');
|
|
47
|
+
if (!element) return undefined;
|
|
48
|
+
|
|
49
|
+
return element.nonce || element.getAttribute('nonce') || undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
// ============================================
|
|
46
53
|
// Version tracking for cache invalidation
|
|
47
54
|
// ============================================
|
|
@@ -417,6 +424,8 @@ export const getRouterScript = () => `
|
|
|
417
424
|
existingStyle.textContent = pageData.css;
|
|
418
425
|
} else {
|
|
419
426
|
const styleEl = document.createElement('style');
|
|
427
|
+
const nonce = getDocumentNonce();
|
|
428
|
+
if (nonce) styleEl.setAttribute('nonce', nonce);
|
|
420
429
|
styleEl.id = 'veryfront-spa-css';
|
|
421
430
|
styleEl.textContent = pageData.css;
|
|
422
431
|
document.head.appendChild(styleEl);
|
|
@@ -25,6 +25,7 @@ import "../../../_dnt.polyfills.js";
|
|
|
25
25
|
import React, { useEffect, useRef } from "react";
|
|
26
26
|
import { collectHead } from "../head-collector.js";
|
|
27
27
|
import { isServerEnvironment } from "../../platform/compat/runtime.js";
|
|
28
|
+
import { getDocumentNonce } from "./ai/csp-nonce.js";
|
|
28
29
|
|
|
29
30
|
export function Head({ children }: { children: React.ReactNode }): React.ReactElement {
|
|
30
31
|
const mountedRef = useRef(false);
|
|
@@ -94,6 +95,7 @@ export function Head({ children }: { children: React.ReactNode }): React.ReactEl
|
|
|
94
95
|
if (!children) return;
|
|
95
96
|
|
|
96
97
|
const addedElements: Element[] = [];
|
|
98
|
+
const nonce = getDocumentNonce();
|
|
97
99
|
|
|
98
100
|
React.Children.forEach(children, (child) => {
|
|
99
101
|
if (!React.isValidElement(child)) return;
|
|
@@ -109,6 +111,9 @@ export function Head({ children }: { children: React.ReactNode }): React.ReactEl
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
const element = document.createElement(type);
|
|
114
|
+
if ((type === "style" || type === "script") && !props.nonce && nonce) {
|
|
115
|
+
element.setAttribute("nonce", nonce);
|
|
116
|
+
}
|
|
112
117
|
|
|
113
118
|
// For scripts, check if already SSR'd via <Head> to avoid double execution
|
|
114
119
|
if (type === "script") {
|
|
@@ -11,6 +11,7 @@ import * as React from "react";
|
|
|
11
11
|
import { ChatContainer } from "../../../../primitives/index.js";
|
|
12
12
|
import type { UIMessage } from "../../../../../agent/react/index.js";
|
|
13
13
|
import type { ChatTheme } from "../../theme.js";
|
|
14
|
+
import { getDocumentNonce } from "../../csp-nonce.js";
|
|
14
15
|
import { cn, defaultChatTheme, generateTokenCSS, mergeThemes } from "../../theme.js";
|
|
15
16
|
import type { ModelOption } from "../../model-selector.js";
|
|
16
17
|
import type { AttachmentInfo } from "../components/attachment-pill.js";
|
|
@@ -97,6 +98,7 @@ export const ChatRoot = React.forwardRef<HTMLDivElement, ChatRootProps>(
|
|
|
97
98
|
ref,
|
|
98
99
|
) {
|
|
99
100
|
const theme = React.useMemo(() => mergeThemes(defaultChatTheme, userTheme), [userTheme]);
|
|
101
|
+
const nonce = getDocumentNonce();
|
|
100
102
|
const tokenCSS = React.useMemo(() => generateTokenCSS(), []);
|
|
101
103
|
const [isAtBottom, _setIsAtBottom] = React.useState(true);
|
|
102
104
|
const scrollAreaRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -164,7 +166,7 @@ export const ChatRoot = React.forwardRef<HTMLDivElement, ChatRootProps>(
|
|
|
164
166
|
|
|
165
167
|
return (
|
|
166
168
|
<ChatContextProvider value={contextValue}>
|
|
167
|
-
<style dangerouslySetInnerHTML={{ __html: tokenCSS }} />
|
|
169
|
+
<style nonce={nonce} dangerouslySetInnerHTML={{ __html: tokenCSS }} />
|
|
168
170
|
<ChatContainer
|
|
169
171
|
ref={ref}
|
|
170
172
|
data-vf-chat=""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { getDocumentNonce } from "./csp-nonce.js";
|
|
2
3
|
import { cn, generateTokenCSS } from "./theme.js";
|
|
3
4
|
import { Chat, type ChatProps } from "./chat/index.js";
|
|
4
5
|
import { ChatSidebar } from "./chat/components/sidebar.js";
|
|
@@ -142,6 +143,7 @@ export const ChatWithSidebar = React.forwardRef<HTMLDivElement, ChatWithSidebarP
|
|
|
142
143
|
},
|
|
143
144
|
ref,
|
|
144
145
|
): React.ReactElement {
|
|
146
|
+
const nonce = getDocumentNonce();
|
|
145
147
|
const storageKey = sidebar?.storageKey;
|
|
146
148
|
const controlledOpen = sidebar?.open;
|
|
147
149
|
const onSidebarToggle = sidebar?.onToggle;
|
|
@@ -320,7 +322,7 @@ export const ChatWithSidebar = React.forwardRef<HTMLDivElement, ChatWithSidebarP
|
|
|
320
322
|
className={cn("flex h-full bg-[var(--background)]", className)}
|
|
321
323
|
data-vf-chat=""
|
|
322
324
|
>
|
|
323
|
-
<style dangerouslySetInnerHTML={{ __html: tokenCSS }} />
|
|
325
|
+
<style nonce={nonce} dangerouslySetInnerHTML={{ __html: tokenCSS }} />
|
|
324
326
|
{sidebarOpen && (
|
|
325
327
|
<ChatSidebar
|
|
326
328
|
threads={threads}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reuse the server-issued CSP nonce for client-created style/script elements
|
|
3
|
+
* during hydration and SPA updates.
|
|
4
|
+
*/
|
|
5
|
+
export function getDocumentNonce(): string | undefined {
|
|
6
|
+
if (typeof document === "undefined") return undefined;
|
|
7
|
+
|
|
8
|
+
const element = document.querySelector<HTMLElement>("script[nonce], style[nonce], link[nonce]");
|
|
9
|
+
if (!element) return undefined;
|
|
10
|
+
|
|
11
|
+
const nonce = element.nonce || element.getAttribute("nonce") || "";
|
|
12
|
+
return nonce || undefined;
|
|
13
|
+
}
|
|
@@ -20,9 +20,16 @@ export function generateNonce(): string {
|
|
|
20
20
|
*
|
|
21
21
|
* - Scripts: nonce-based + cdn.jsdelivr.net + esm.sh (Scalar API docs,
|
|
22
22
|
* html2canvas, legacy/browser ESM hydration)
|
|
23
|
-
* - Styles:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* - Styles:
|
|
24
|
+
* - style-src: 'self' + 'unsafe-inline' + Google Fonts + cdn.veryfront.com
|
|
25
|
+
* so React style="" attributes and framework inline styles remain
|
|
26
|
+
* compatible. Do not include a nonce in style-src here: browsers ignore
|
|
27
|
+
* 'unsafe-inline' when a nonce/hash is present on the directive, which
|
|
28
|
+
* breaks React style attributes.
|
|
29
|
+
* - style-src-elem: nonce-based + Google Fonts + cdn.veryfront.com for
|
|
30
|
+
* inline <style> tags and stylesheet elements
|
|
31
|
+
* - style-src-attr: 'unsafe-inline' for modern browsers with directive-level
|
|
32
|
+
* style attribute support
|
|
26
33
|
* - Images/media/fonts: 'self' + data: + https: + cdn.veryfront.com
|
|
27
34
|
* - Connections: 'self' + wss: + https: (WebSocket for HMR/live reload, API calls)
|
|
28
35
|
* - Objects: 'none' (block Flash/plugins)
|
|
@@ -35,7 +42,8 @@ function buildDefaultCSP(nonce: string): string {
|
|
|
35
42
|
return [
|
|
36
43
|
`default-src 'self'`,
|
|
37
44
|
`script-src 'self' 'nonce-${nonce}' https://cdn.jsdelivr.net https://esm.sh`,
|
|
38
|
-
`style-src 'self' 'unsafe-inline'
|
|
45
|
+
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.veryfront.com`,
|
|
46
|
+
`style-src-elem 'self' 'nonce-${nonce}' https://fonts.googleapis.com https://cdn.veryfront.com`,
|
|
39
47
|
`style-src-attr 'unsafe-inline'`,
|
|
40
48
|
`img-src 'self' data: https:`,
|
|
41
49
|
`font-src 'self' data: https://fonts.gstatic.com https://cdn.veryfront.com`,
|