@vertz/cloudflare 0.2.1 → 0.2.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/.turbo/turbo-build.log +1 -16
- package/CHANGELOG.md +25 -0
- package/dist/handler.d.ts +73 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +196 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +3 -71
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -152
- package/dist/index.js.map +1 -0
- package/package.json +5 -6
- package/src/handler.ts +51 -30
- package/src/index.ts +1 -1
- package/tests/handler.test.ts +165 -1
- package/.turbo/turbo-test.log +0 -11
- package/.turbo/turbo-typecheck.log +0 -1
- package/bunup.config.ts +0 -20
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,16 +1 @@
|
|
|
1
|
-
$
|
|
2
|
-
i Using bunup v0.16.31 and bun v1.3.9
|
|
3
|
-
i Using cloudflare/bunup.config.ts
|
|
4
|
-
i Build started
|
|
5
|
-
|
|
6
|
-
src/index.ts
|
|
7
|
-
|
|
8
|
-
Output Raw Gzip
|
|
9
|
-
|
|
10
|
-
dist/index.js 4.80 KB 1.56 KB
|
|
11
|
-
dist/index.d.ts 2.56 KB 1.12 KB
|
|
12
|
-
|
|
13
|
-
2 files 7.36 KB 2.67 KB
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
✓ Build completed in 341ms
|
|
1
|
+
$ tsc
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @vertz/cloudflare
|
|
2
|
+
|
|
3
|
+
## 0.2.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`a986d07`](https://github.com/vertz-dev/vertz/commit/a986d0788ca0210dfa4f624153d4bda72257a78c)]:
|
|
8
|
+
- @vertz/ui-server@0.2.4
|
|
9
|
+
- @vertz/core@0.2.4
|
|
10
|
+
|
|
11
|
+
## 0.2.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`62dddcb`](https://github.com/vertz-dev/vertz/commit/62dddcbcb4943b12a04bca8466b09ae21901070b), [`2e86c55`](https://github.com/vertz-dev/vertz/commit/2e86c55e3c04f3c534bf0dc124d18dcdc5d9eefc), [`62dddcb`](https://github.com/vertz-dev/vertz/commit/62dddcbcb4943b12a04bca8466b09ae21901070b), [`b0b6115`](https://github.com/vertz-dev/vertz/commit/b0b6115e0389447ffb951e875b5ce224e4ace51c)]:
|
|
16
|
+
- @vertz/core@0.2.3
|
|
17
|
+
- @vertz/ui-server@0.2.3
|
|
18
|
+
|
|
19
|
+
## 0.2.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies []:
|
|
24
|
+
- @vertz/ui-server@0.2.2
|
|
25
|
+
- @vertz/core@0.2.2
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AppBuilder } from '@vertz/core';
|
|
2
|
+
import type { SSRModule } from '@vertz/ui-server/ssr';
|
|
3
|
+
export interface CloudflareHandlerOptions {
|
|
4
|
+
basePath?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* SSR module configuration for zero-boilerplate server-side rendering.
|
|
8
|
+
*
|
|
9
|
+
* Pass the app module directly and the handler generates the HTML template,
|
|
10
|
+
* wires up createSSRHandler, and handles the full SSR pipeline.
|
|
11
|
+
*/
|
|
12
|
+
export interface SSRModuleConfig {
|
|
13
|
+
/** App module exporting App, theme?, styles?, getInjectedCSS? */
|
|
14
|
+
module: SSRModule;
|
|
15
|
+
/** Client-side entry script path. Default: '/assets/entry-client.js' */
|
|
16
|
+
clientScript?: string;
|
|
17
|
+
/** HTML document title. Default: 'Vertz App' */
|
|
18
|
+
title?: string;
|
|
19
|
+
/** SSR query timeout in ms. Default: 5000 (generous for D1 cold starts). */
|
|
20
|
+
ssrTimeout?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Full-stack configuration for createHandler.
|
|
24
|
+
*
|
|
25
|
+
* Supports lazy app initialization (for D1 bindings), SSR fallback,
|
|
26
|
+
* and automatic security headers.
|
|
27
|
+
*/
|
|
28
|
+
export interface CloudflareHandlerConfig {
|
|
29
|
+
/**
|
|
30
|
+
* Factory that creates the AppBuilder. Receives the Worker env bindings.
|
|
31
|
+
* Called once on first request, then cached.
|
|
32
|
+
*/
|
|
33
|
+
app: (env: unknown) => AppBuilder;
|
|
34
|
+
/** API path prefix. Requests matching this prefix go to the app handler. */
|
|
35
|
+
basePath: string;
|
|
36
|
+
/**
|
|
37
|
+
* SSR configuration for non-API routes.
|
|
38
|
+
*
|
|
39
|
+
* - `SSRModuleConfig` — zero-boilerplate: pass the app module directly
|
|
40
|
+
* - `(request: Request) => Promise<Response>` — custom SSR callback
|
|
41
|
+
* - `undefined` — non-API requests return 404
|
|
42
|
+
*/
|
|
43
|
+
ssr?: SSRModuleConfig | ((request: Request) => Promise<Response>);
|
|
44
|
+
/** When true, adds standard security headers to all responses. */
|
|
45
|
+
securityHeaders?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Generate a cryptographically random nonce for CSP. */
|
|
48
|
+
export declare function generateNonce(): string;
|
|
49
|
+
/**
|
|
50
|
+
* Create a Cloudflare Worker handler from a Vertz app.
|
|
51
|
+
*
|
|
52
|
+
* Simple form — wraps an AppBuilder directly:
|
|
53
|
+
* ```ts
|
|
54
|
+
* export default createHandler(app, { basePath: '/api' });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Config form — full-stack with lazy init, SSR, and security headers:
|
|
58
|
+
* ```ts
|
|
59
|
+
* export default createHandler({
|
|
60
|
+
* app: (env) => createServer({ entities, db: createDb({ d1: env.DB }) }),
|
|
61
|
+
* basePath: '/api',
|
|
62
|
+
* ssr: (req) => renderToString(new URL(req.url).pathname),
|
|
63
|
+
* securityHeaders: true,
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
/** Worker module shape returned by createHandler. */
|
|
68
|
+
export interface CloudflareWorkerModule {
|
|
69
|
+
fetch(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response>;
|
|
70
|
+
}
|
|
71
|
+
export declare function createHandler(appOrConfig: AppBuilder | CloudflareHandlerConfig, options?: CloudflareHandlerOptions): CloudflareWorkerModule;
|
|
72
|
+
export declare function generateHTMLTemplate(clientScript: string, title: string, nonce?: string): string;
|
|
73
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAMtD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,MAAM,EAAE,SAAS,CAAC;IAClB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,UAAU,CAAC;IAElC,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElE,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,yDAAyD;AACzD,wBAAgB,aAAa,IAAI,MAAM,CAStC;AA4CD;;;;;;;;;;;;;;;;;GAiBG;AACH,qDAAqD;AACrD,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACjF;AAED,wBAAgB,aAAa,CAC3B,WAAW,EAAE,UAAU,GAAG,uBAAuB,EACjD,OAAO,CAAC,EAAE,wBAAwB,GACjC,sBAAsB,CAQxB;AA+BD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAchG"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Security headers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/** Generate a cryptographically random nonce for CSP. */
|
|
5
|
+
export function generateNonce() {
|
|
6
|
+
const bytes = new Uint8Array(16);
|
|
7
|
+
crypto.getRandomValues(bytes);
|
|
8
|
+
// Base64-encode without padding for a compact, URL-safe nonce
|
|
9
|
+
let binary = '';
|
|
10
|
+
for (const byte of bytes) {
|
|
11
|
+
binary += String.fromCharCode(byte);
|
|
12
|
+
}
|
|
13
|
+
return btoa(binary);
|
|
14
|
+
}
|
|
15
|
+
const STATIC_SECURITY_HEADERS = {
|
|
16
|
+
'X-Content-Type-Options': 'nosniff',
|
|
17
|
+
'X-Frame-Options': 'DENY',
|
|
18
|
+
'X-XSS-Protection': '1; mode=block',
|
|
19
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
20
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
21
|
+
};
|
|
22
|
+
function buildCSPHeader(nonce) {
|
|
23
|
+
return `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;`;
|
|
24
|
+
}
|
|
25
|
+
function withSecurityHeaders(response, nonce) {
|
|
26
|
+
const headers = new Headers(response.headers);
|
|
27
|
+
for (const [key, value] of Object.entries(STATIC_SECURITY_HEADERS)) {
|
|
28
|
+
headers.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
headers.set('Content-Security-Policy', buildCSPHeader(nonce));
|
|
31
|
+
return new Response(response.body, {
|
|
32
|
+
status: response.status,
|
|
33
|
+
statusText: response.statusText,
|
|
34
|
+
headers,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
function stripBasePath(request, basePath) {
|
|
41
|
+
const url = new URL(request.url);
|
|
42
|
+
if (url.pathname.startsWith(basePath)) {
|
|
43
|
+
url.pathname = url.pathname.slice(basePath.length) || '/';
|
|
44
|
+
return new Request(url.toString(), request);
|
|
45
|
+
}
|
|
46
|
+
return request;
|
|
47
|
+
}
|
|
48
|
+
export function createHandler(appOrConfig, options) {
|
|
49
|
+
// Config object form
|
|
50
|
+
if ('app' in appOrConfig && typeof appOrConfig.app === 'function') {
|
|
51
|
+
return createFullStackHandler(appOrConfig);
|
|
52
|
+
}
|
|
53
|
+
// Simple AppBuilder form (backward compat)
|
|
54
|
+
return createSimpleHandler(appOrConfig, options);
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Simple handler (backward compat)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
function createSimpleHandler(app, options) {
|
|
60
|
+
const handler = app.handler;
|
|
61
|
+
return {
|
|
62
|
+
async fetch(request, _env, _ctx) {
|
|
63
|
+
if (options?.basePath) {
|
|
64
|
+
request = stripBasePath(request, options.basePath);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return await handler(request);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Unhandled error in worker:', error);
|
|
71
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// HTML template generation
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
export function generateHTMLTemplate(clientScript, title, nonce) {
|
|
80
|
+
const nonceAttr = nonce != null ? ` nonce="${nonce}"` : '';
|
|
81
|
+
return `<!doctype html>
|
|
82
|
+
<html lang="en">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="UTF-8">
|
|
85
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
86
|
+
<title>${title}</title>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div id="app"><!--ssr-outlet--></div>
|
|
90
|
+
<script type="module" src="${clientScript}"${nonceAttr}></script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>`;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Full-stack handler
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
function isSSRModuleConfig(ssr) {
|
|
98
|
+
return typeof ssr === 'object' && 'module' in ssr;
|
|
99
|
+
}
|
|
100
|
+
function createFullStackHandler(config) {
|
|
101
|
+
const { basePath, ssr, securityHeaders } = config;
|
|
102
|
+
let cachedApp = null;
|
|
103
|
+
// SSR handler factory: when using SSRModuleConfig with nonce support, this
|
|
104
|
+
// is called per-request with the current nonce. For custom callbacks it
|
|
105
|
+
// is set once and always returns the same handler.
|
|
106
|
+
let ssrHandlerFactory = null;
|
|
107
|
+
let ssrResolved = false;
|
|
108
|
+
function getApp(env) {
|
|
109
|
+
if (!cachedApp) {
|
|
110
|
+
cachedApp = config.app(env);
|
|
111
|
+
}
|
|
112
|
+
return cachedApp;
|
|
113
|
+
}
|
|
114
|
+
async function resolveSSR() {
|
|
115
|
+
if (ssrResolved)
|
|
116
|
+
return;
|
|
117
|
+
ssrResolved = true;
|
|
118
|
+
if (!ssr)
|
|
119
|
+
return;
|
|
120
|
+
if (isSSRModuleConfig(ssr)) {
|
|
121
|
+
const { createSSRHandler } = await import('@vertz/ui-server/ssr');
|
|
122
|
+
const { module, clientScript = '/assets/entry-client.js', title = 'Vertz App', ssrTimeout = 5000, } = ssr;
|
|
123
|
+
// Return a factory that creates an SSR handler with the per-request nonce
|
|
124
|
+
ssrHandlerFactory = (nonce) => createSSRHandler({
|
|
125
|
+
module,
|
|
126
|
+
template: generateHTMLTemplate(clientScript, title, nonce),
|
|
127
|
+
ssrTimeout,
|
|
128
|
+
nonce,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Custom callback — wrap it in a factory that ignores the nonce
|
|
133
|
+
ssrHandlerFactory = () => ssr;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function applyHeaders(response, nonce) {
|
|
137
|
+
return securityHeaders ? withSecurityHeaders(response, nonce) : response;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
async fetch(request, env, _ctx) {
|
|
141
|
+
await resolveSSR();
|
|
142
|
+
const url = new URL(request.url);
|
|
143
|
+
const nonce = generateNonce();
|
|
144
|
+
// Route splitting: basePath/* → API handler (no URL rewriting — the
|
|
145
|
+
// app's own basePath/apiPrefix handles prefix matching internally)
|
|
146
|
+
if (url.pathname.startsWith(basePath)) {
|
|
147
|
+
try {
|
|
148
|
+
const app = getApp(env);
|
|
149
|
+
const response = await app.handler(request);
|
|
150
|
+
return applyHeaders(response, nonce);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Unhandled error in worker:', error);
|
|
154
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Non-API routes → SSR or 404
|
|
158
|
+
if (ssrHandlerFactory) {
|
|
159
|
+
const app = getApp(env);
|
|
160
|
+
const ssrHandler = ssrHandlerFactory(nonce);
|
|
161
|
+
const origin = url.origin;
|
|
162
|
+
// Patch fetch during SSR so API requests (e.g. query() calling
|
|
163
|
+
// fetch('/api/todos')) are routed through the in-memory app handler
|
|
164
|
+
// instead of attempting a network self-fetch (which fails on Workers).
|
|
165
|
+
const originalFetch = globalThis.fetch;
|
|
166
|
+
globalThis.fetch = (input, init) => {
|
|
167
|
+
// Determine the pathname from the input (string, URL, or Request)
|
|
168
|
+
const rawUrl = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
169
|
+
const isRelative = rawUrl.startsWith('/');
|
|
170
|
+
const pathname = isRelative ? rawUrl.split('?')[0] : new URL(rawUrl).pathname;
|
|
171
|
+
const isLocal = isRelative || new URL(rawUrl).origin === origin;
|
|
172
|
+
if (isLocal && pathname.startsWith(basePath)) {
|
|
173
|
+
// Build an absolute URL so Request() doesn't reject relative URLs
|
|
174
|
+
const absoluteUrl = isRelative ? `${origin}${rawUrl}` : rawUrl;
|
|
175
|
+
const req = new Request(absoluteUrl, init);
|
|
176
|
+
return app.handler(req);
|
|
177
|
+
}
|
|
178
|
+
return originalFetch(input, init);
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
const response = await ssrHandler(request);
|
|
182
|
+
return applyHeaders(response, nonce);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error('Unhandled error in worker:', error);
|
|
186
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
globalThis.fetch = originalFetch;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return applyHeaders(new Response('Not Found', { status: 404 }), nonce);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,71 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
basePath?: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* SSR module configuration for zero-boilerplate server-side rendering.
|
|
8
|
-
*
|
|
9
|
-
* Pass the app module directly and the handler generates the HTML template,
|
|
10
|
-
* wires up createSSRHandler, and handles the full SSR pipeline.
|
|
11
|
-
*/
|
|
12
|
-
interface SSRModuleConfig {
|
|
13
|
-
/** App module exporting App, theme?, styles?, getInjectedCSS? */
|
|
14
|
-
module: SSRModule;
|
|
15
|
-
/** Client-side entry script path. Default: '/assets/entry-client.js' */
|
|
16
|
-
clientScript?: string;
|
|
17
|
-
/** HTML document title. Default: 'Vertz App' */
|
|
18
|
-
title?: string;
|
|
19
|
-
/** SSR query timeout in ms. Default: 5000 (generous for D1 cold starts). */
|
|
20
|
-
ssrTimeout?: number;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Full-stack configuration for createHandler.
|
|
24
|
-
*
|
|
25
|
-
* Supports lazy app initialization (for D1 bindings), SSR fallback,
|
|
26
|
-
* and automatic security headers.
|
|
27
|
-
*/
|
|
28
|
-
interface CloudflareHandlerConfig {
|
|
29
|
-
/**
|
|
30
|
-
* Factory that creates the AppBuilder. Receives the Worker env bindings.
|
|
31
|
-
* Called once on first request, then cached.
|
|
32
|
-
*/
|
|
33
|
-
app: (env: unknown) => AppBuilder;
|
|
34
|
-
/** API path prefix. Requests matching this prefix go to the app handler. */
|
|
35
|
-
basePath: string;
|
|
36
|
-
/**
|
|
37
|
-
* SSR configuration for non-API routes.
|
|
38
|
-
*
|
|
39
|
-
* - `SSRModuleConfig` — zero-boilerplate: pass the app module directly
|
|
40
|
-
* - `(request: Request) => Promise<Response>` — custom SSR callback
|
|
41
|
-
* - `undefined` — non-API requests return 404
|
|
42
|
-
*/
|
|
43
|
-
ssr?: SSRModuleConfig | ((request: Request) => Promise<Response>);
|
|
44
|
-
/** When true, adds standard security headers to all responses. */
|
|
45
|
-
securityHeaders?: boolean;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Create a Cloudflare Worker handler from a Vertz app.
|
|
49
|
-
*
|
|
50
|
-
* Simple form — wraps an AppBuilder directly:
|
|
51
|
-
* ```ts
|
|
52
|
-
* createHandler(app, { basePath: '/api' });
|
|
53
|
-
* ```
|
|
54
|
-
*
|
|
55
|
-
* Config form — full-stack with lazy init, SSR, and security headers:
|
|
56
|
-
* ```ts
|
|
57
|
-
* createHandler({
|
|
58
|
-
* app: (env) => createServer({ entities, db: createDb({ d1: env.DB }) }),
|
|
59
|
-
* basePath: '/api',
|
|
60
|
-
* ssr: (req) => renderToString(new URL(req.url).pathname),
|
|
61
|
-
* securityHeaders: true,
|
|
62
|
-
* });
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
/** Worker module shape returned by createHandler. */
|
|
66
|
-
interface CloudflareWorkerModule {
|
|
67
|
-
fetch(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response>;
|
|
68
|
-
}
|
|
69
|
-
declare function createHandler(appOrConfig: AppBuilder | CloudflareHandlerConfig, options?: CloudflareHandlerOptions): CloudflareWorkerModule;
|
|
70
|
-
declare function generateHTMLTemplate(clientScript: string, title: string): string;
|
|
71
|
-
export { generateHTMLTemplate, createHandler, SSRModuleConfig, CloudflareWorkerModule, CloudflareHandlerOptions, CloudflareHandlerConfig };
|
|
1
|
+
export type { CloudflareHandlerConfig, CloudflareHandlerOptions, CloudflareWorkerModule, SSRModuleConfig, } from './handler.js';
|
|
2
|
+
export { createHandler, generateHTMLTemplate, generateNonce } from './handler.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,152 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var SECURITY_HEADERS = {
|
|
4
|
-
"X-Content-Type-Options": "nosniff",
|
|
5
|
-
"X-Frame-Options": "DENY",
|
|
6
|
-
"X-XSS-Protection": "1; mode=block",
|
|
7
|
-
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
8
|
-
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
|
|
9
|
-
};
|
|
10
|
-
function withSecurityHeaders(response) {
|
|
11
|
-
const headers = new Headers(response.headers);
|
|
12
|
-
for (const [key, value] of Object.entries(SECURITY_HEADERS)) {
|
|
13
|
-
headers.set(key, value);
|
|
14
|
-
}
|
|
15
|
-
return new Response(response.body, {
|
|
16
|
-
status: response.status,
|
|
17
|
-
statusText: response.statusText,
|
|
18
|
-
headers
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function stripBasePath(request, basePath) {
|
|
22
|
-
const url = new URL(request.url);
|
|
23
|
-
if (url.pathname.startsWith(basePath)) {
|
|
24
|
-
url.pathname = url.pathname.slice(basePath.length) || "/";
|
|
25
|
-
return new Request(url.toString(), request);
|
|
26
|
-
}
|
|
27
|
-
return request;
|
|
28
|
-
}
|
|
29
|
-
function createHandler(appOrConfig, options) {
|
|
30
|
-
if ("app" in appOrConfig && typeof appOrConfig.app === "function") {
|
|
31
|
-
return createFullStackHandler(appOrConfig);
|
|
32
|
-
}
|
|
33
|
-
return createSimpleHandler(appOrConfig, options);
|
|
34
|
-
}
|
|
35
|
-
function createSimpleHandler(app, options) {
|
|
36
|
-
const handler = app.handler;
|
|
37
|
-
return {
|
|
38
|
-
async fetch(request, _env, _ctx) {
|
|
39
|
-
if (options?.basePath) {
|
|
40
|
-
request = stripBasePath(request, options.basePath);
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
return await handler(request);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error("Unhandled error in worker:", error);
|
|
46
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function generateHTMLTemplate(clientScript, title) {
|
|
52
|
-
return `<!doctype html>
|
|
53
|
-
<html lang="en">
|
|
54
|
-
<head>
|
|
55
|
-
<meta charset="UTF-8">
|
|
56
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
57
|
-
<title>${title}</title>
|
|
58
|
-
</head>
|
|
59
|
-
<body>
|
|
60
|
-
<div id="app"><!--ssr-outlet--></div>
|
|
61
|
-
<script type="module" src="${clientScript}"></script>
|
|
62
|
-
</body>
|
|
63
|
-
</html>`;
|
|
64
|
-
}
|
|
65
|
-
function isSSRModuleConfig(ssr) {
|
|
66
|
-
return typeof ssr === "object" && "module" in ssr;
|
|
67
|
-
}
|
|
68
|
-
function createFullStackHandler(config) {
|
|
69
|
-
const { basePath, ssr, securityHeaders } = config;
|
|
70
|
-
let cachedApp = null;
|
|
71
|
-
let ssrHandler = null;
|
|
72
|
-
let ssrResolved = false;
|
|
73
|
-
function getApp(env) {
|
|
74
|
-
if (!cachedApp) {
|
|
75
|
-
cachedApp = config.app(env);
|
|
76
|
-
}
|
|
77
|
-
return cachedApp;
|
|
78
|
-
}
|
|
79
|
-
async function resolveSSR() {
|
|
80
|
-
if (ssrResolved)
|
|
81
|
-
return;
|
|
82
|
-
ssrResolved = true;
|
|
83
|
-
if (!ssr)
|
|
84
|
-
return;
|
|
85
|
-
if (isSSRModuleConfig(ssr)) {
|
|
86
|
-
const { createSSRHandler } = await import("@vertz/ui-server/ssr");
|
|
87
|
-
const {
|
|
88
|
-
module,
|
|
89
|
-
clientScript = "/assets/entry-client.js",
|
|
90
|
-
title = "Vertz App",
|
|
91
|
-
ssrTimeout = 5000
|
|
92
|
-
} = ssr;
|
|
93
|
-
ssrHandler = createSSRHandler({
|
|
94
|
-
module,
|
|
95
|
-
template: generateHTMLTemplate(clientScript, title),
|
|
96
|
-
ssrTimeout
|
|
97
|
-
});
|
|
98
|
-
} else {
|
|
99
|
-
ssrHandler = ssr;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function applyHeaders(response) {
|
|
103
|
-
return securityHeaders ? withSecurityHeaders(response) : response;
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
async fetch(request, env, _ctx) {
|
|
107
|
-
await resolveSSR();
|
|
108
|
-
const url = new URL(request.url);
|
|
109
|
-
if (url.pathname.startsWith(basePath)) {
|
|
110
|
-
try {
|
|
111
|
-
const app = getApp(env);
|
|
112
|
-
const response = await app.handler(request);
|
|
113
|
-
return applyHeaders(response);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error("Unhandled error in worker:", error);
|
|
116
|
-
return applyHeaders(new Response("Internal Server Error", { status: 500 }));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (ssrHandler) {
|
|
120
|
-
const app = getApp(env);
|
|
121
|
-
const origin = url.origin;
|
|
122
|
-
const originalFetch = globalThis.fetch;
|
|
123
|
-
globalThis.fetch = (input, init) => {
|
|
124
|
-
const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
125
|
-
const isRelative = rawUrl.startsWith("/");
|
|
126
|
-
const pathname = isRelative ? rawUrl.split("?")[0] : new URL(rawUrl).pathname;
|
|
127
|
-
const isLocal = isRelative || new URL(rawUrl).origin === origin;
|
|
128
|
-
if (isLocal && pathname.startsWith(basePath)) {
|
|
129
|
-
const absoluteUrl = isRelative ? `${origin}${rawUrl}` : rawUrl;
|
|
130
|
-
const req = new Request(absoluteUrl, init);
|
|
131
|
-
return app.handler(req);
|
|
132
|
-
}
|
|
133
|
-
return originalFetch(input, init);
|
|
134
|
-
};
|
|
135
|
-
try {
|
|
136
|
-
const response = await ssrHandler(request);
|
|
137
|
-
return applyHeaders(response);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error("Unhandled error in worker:", error);
|
|
140
|
-
return applyHeaders(new Response("Internal Server Error", { status: 500 }));
|
|
141
|
-
} finally {
|
|
142
|
-
globalThis.fetch = originalFetch;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return applyHeaders(new Response("Not Found", { status: 404 }));
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
export {
|
|
150
|
-
generateHTMLTemplate,
|
|
151
|
-
createHandler
|
|
152
|
-
};
|
|
1
|
+
export { createHandler, generateHTMLTemplate, generateNonce } from './handler.js';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/cloudflare",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Cloudflare Workers adapter for vertz",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,22 +17,21 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "
|
|
20
|
+
"build": "tsc",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"test": "vitest run"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@vertz/core": "0.2.
|
|
25
|
+
"@vertz/core": "0.2.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@cloudflare/workers-types": "^4.20260305.0",
|
|
29
|
-
"@vertz/ui-server": "0.2.
|
|
29
|
+
"@vertz/ui-server": "0.2.2",
|
|
30
30
|
"@vitest/coverage-v8": "^4.0.18",
|
|
31
|
-
"bunup": "latest",
|
|
32
31
|
"typescript": "^5.7.3"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
|
-
"@vertz/ui-server": "0.2.
|
|
34
|
+
"@vertz/ui-server": "0.2.2"
|
|
36
35
|
},
|
|
37
36
|
"peerDependenciesMeta": {
|
|
38
37
|
"@vertz/ui-server": {
|
package/src/handler.ts
CHANGED
|
@@ -59,20 +59,36 @@ export interface CloudflareHandlerConfig {
|
|
|
59
59
|
// Security headers
|
|
60
60
|
// ---------------------------------------------------------------------------
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
/** Generate a cryptographically random nonce for CSP. */
|
|
63
|
+
export function generateNonce(): string {
|
|
64
|
+
const bytes = new Uint8Array(16);
|
|
65
|
+
crypto.getRandomValues(bytes);
|
|
66
|
+
// Base64-encode without padding for a compact, URL-safe nonce
|
|
67
|
+
let binary = '';
|
|
68
|
+
for (const byte of bytes) {
|
|
69
|
+
binary += String.fromCharCode(byte);
|
|
70
|
+
}
|
|
71
|
+
return btoa(binary);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const STATIC_SECURITY_HEADERS: Record<string, string> = {
|
|
63
75
|
'X-Content-Type-Options': 'nosniff',
|
|
64
76
|
'X-Frame-Options': 'DENY',
|
|
65
77
|
'X-XSS-Protection': '1; mode=block',
|
|
66
78
|
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
67
|
-
'
|
|
68
|
-
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;",
|
|
79
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
69
80
|
};
|
|
70
81
|
|
|
71
|
-
function
|
|
82
|
+
function buildCSPHeader(nonce: string): string {
|
|
83
|
+
return `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function withSecurityHeaders(response: Response, nonce: string): Response {
|
|
72
87
|
const headers = new Headers(response.headers);
|
|
73
|
-
for (const [key, value] of Object.entries(
|
|
88
|
+
for (const [key, value] of Object.entries(STATIC_SECURITY_HEADERS)) {
|
|
74
89
|
headers.set(key, value);
|
|
75
90
|
}
|
|
91
|
+
headers.set('Content-Security-Policy', buildCSPHeader(nonce));
|
|
76
92
|
return new Response(response.body, {
|
|
77
93
|
status: response.status,
|
|
78
94
|
statusText: response.statusText,
|
|
@@ -162,7 +178,8 @@ function createSimpleHandler(
|
|
|
162
178
|
// HTML template generation
|
|
163
179
|
// ---------------------------------------------------------------------------
|
|
164
180
|
|
|
165
|
-
export function generateHTMLTemplate(clientScript: string, title: string): string {
|
|
181
|
+
export function generateHTMLTemplate(clientScript: string, title: string, nonce?: string): string {
|
|
182
|
+
const nonceAttr = nonce != null ? ` nonce="${nonce}"` : '';
|
|
166
183
|
return `<!doctype html>
|
|
167
184
|
<html lang="en">
|
|
168
185
|
<head>
|
|
@@ -172,7 +189,7 @@ export function generateHTMLTemplate(clientScript: string, title: string): strin
|
|
|
172
189
|
</head>
|
|
173
190
|
<body>
|
|
174
191
|
<div id="app"><!--ssr-outlet--></div>
|
|
175
|
-
<script type="module" src="${clientScript}"></script>
|
|
192
|
+
<script type="module" src="${clientScript}"${nonceAttr}></script>
|
|
176
193
|
</body>
|
|
177
194
|
</html>`;
|
|
178
195
|
}
|
|
@@ -190,7 +207,11 @@ function isSSRModuleConfig(
|
|
|
190
207
|
function createFullStackHandler(config: CloudflareHandlerConfig): CloudflareWorkerModule {
|
|
191
208
|
const { basePath, ssr, securityHeaders } = config;
|
|
192
209
|
let cachedApp: AppBuilder | null = null;
|
|
193
|
-
|
|
210
|
+
// SSR handler factory: when using SSRModuleConfig with nonce support, this
|
|
211
|
+
// is called per-request with the current nonce. For custom callbacks it
|
|
212
|
+
// is set once and always returns the same handler.
|
|
213
|
+
let ssrHandlerFactory: ((nonce?: string) => (request: Request) => Promise<Response>) | null =
|
|
214
|
+
null;
|
|
194
215
|
let ssrResolved = false;
|
|
195
216
|
|
|
196
217
|
function getApp(env: unknown): AppBuilder {
|
|
@@ -214,24 +235,29 @@ function createFullStackHandler(config: CloudflareHandlerConfig): CloudflareWork
|
|
|
214
235
|
title = 'Vertz App',
|
|
215
236
|
ssrTimeout = 5000,
|
|
216
237
|
} = ssr;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
// Return a factory that creates an SSR handler with the per-request nonce
|
|
239
|
+
ssrHandlerFactory = (nonce?: string) =>
|
|
240
|
+
createSSRHandler({
|
|
241
|
+
module,
|
|
242
|
+
template: generateHTMLTemplate(clientScript, title, nonce),
|
|
243
|
+
ssrTimeout,
|
|
244
|
+
nonce,
|
|
245
|
+
});
|
|
222
246
|
} else {
|
|
223
|
-
|
|
247
|
+
// Custom callback — wrap it in a factory that ignores the nonce
|
|
248
|
+
ssrHandlerFactory = () => ssr;
|
|
224
249
|
}
|
|
225
250
|
}
|
|
226
251
|
|
|
227
|
-
function applyHeaders(response: Response): Response {
|
|
228
|
-
return securityHeaders ? withSecurityHeaders(response) : response;
|
|
252
|
+
function applyHeaders(response: Response, nonce: string): Response {
|
|
253
|
+
return securityHeaders ? withSecurityHeaders(response, nonce) : response;
|
|
229
254
|
}
|
|
230
255
|
|
|
231
256
|
return {
|
|
232
257
|
async fetch(request: Request, env: unknown, _ctx: ExecutionContext): Promise<Response> {
|
|
233
258
|
await resolveSSR();
|
|
234
259
|
const url = new URL(request.url);
|
|
260
|
+
const nonce = generateNonce();
|
|
235
261
|
|
|
236
262
|
// Route splitting: basePath/* → API handler (no URL rewriting — the
|
|
237
263
|
// app's own basePath/apiPrefix handles prefix matching internally)
|
|
@@ -239,16 +265,17 @@ function createFullStackHandler(config: CloudflareHandlerConfig): CloudflareWork
|
|
|
239
265
|
try {
|
|
240
266
|
const app = getApp(env);
|
|
241
267
|
const response = await app.handler(request);
|
|
242
|
-
return applyHeaders(response);
|
|
268
|
+
return applyHeaders(response, nonce);
|
|
243
269
|
} catch (error) {
|
|
244
270
|
console.error('Unhandled error in worker:', error);
|
|
245
|
-
return applyHeaders(new Response('Internal Server Error', { status: 500 }));
|
|
271
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
246
272
|
}
|
|
247
273
|
}
|
|
248
274
|
|
|
249
275
|
// Non-API routes → SSR or 404
|
|
250
|
-
if (
|
|
276
|
+
if (ssrHandlerFactory) {
|
|
251
277
|
const app = getApp(env);
|
|
278
|
+
const ssrHandler = ssrHandlerFactory(nonce);
|
|
252
279
|
const origin = url.origin;
|
|
253
280
|
// Patch fetch during SSR so API requests (e.g. query() calling
|
|
254
281
|
// fetch('/api/todos')) are routed through the in-memory app handler
|
|
@@ -257,15 +284,9 @@ function createFullStackHandler(config: CloudflareHandlerConfig): CloudflareWork
|
|
|
257
284
|
globalThis.fetch = (input, init) => {
|
|
258
285
|
// Determine the pathname from the input (string, URL, or Request)
|
|
259
286
|
const rawUrl =
|
|
260
|
-
typeof input === 'string'
|
|
261
|
-
? input
|
|
262
|
-
: input instanceof URL
|
|
263
|
-
? input.href
|
|
264
|
-
: input.url;
|
|
287
|
+
typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
265
288
|
const isRelative = rawUrl.startsWith('/');
|
|
266
|
-
const pathname = isRelative
|
|
267
|
-
? rawUrl.split('?')[0]
|
|
268
|
-
: new URL(rawUrl).pathname;
|
|
289
|
+
const pathname = isRelative ? rawUrl.split('?')[0] : new URL(rawUrl).pathname;
|
|
269
290
|
const isLocal = isRelative || new URL(rawUrl).origin === origin;
|
|
270
291
|
|
|
271
292
|
if (isLocal && pathname.startsWith(basePath)) {
|
|
@@ -278,16 +299,16 @@ function createFullStackHandler(config: CloudflareHandlerConfig): CloudflareWork
|
|
|
278
299
|
};
|
|
279
300
|
try {
|
|
280
301
|
const response = await ssrHandler(request);
|
|
281
|
-
return applyHeaders(response);
|
|
302
|
+
return applyHeaders(response, nonce);
|
|
282
303
|
} catch (error) {
|
|
283
304
|
console.error('Unhandled error in worker:', error);
|
|
284
|
-
return applyHeaders(new Response('Internal Server Error', { status: 500 }));
|
|
305
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
285
306
|
} finally {
|
|
286
307
|
globalThis.fetch = originalFetch;
|
|
287
308
|
}
|
|
288
309
|
}
|
|
289
310
|
|
|
290
|
-
return applyHeaders(new Response('Not Found', { status: 404 }));
|
|
311
|
+
return applyHeaders(new Response('Not Found', { status: 404 }), nonce);
|
|
291
312
|
},
|
|
292
313
|
};
|
|
293
314
|
}
|
package/src/index.ts
CHANGED
package/tests/handler.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AppBuilder } from '@vertz/core';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { createHandler, generateHTMLTemplate } from '../src/handler.js';
|
|
3
|
+
import { createHandler, generateHTMLTemplate, generateNonce } from '../src/handler.js';
|
|
4
4
|
|
|
5
5
|
function mockApp(handler?: (...args: unknown[]) => Promise<Response>): AppBuilder {
|
|
6
6
|
return {
|
|
@@ -509,3 +509,167 @@ describe('createHandler (SSR module config)', () => {
|
|
|
509
509
|
expect(await response.text()).toBe('{"items":[]}');
|
|
510
510
|
});
|
|
511
511
|
});
|
|
512
|
+
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
// Nonce-based CSP
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
describe('generateNonce', () => {
|
|
518
|
+
it('returns a base64-encoded string', () => {
|
|
519
|
+
const nonce = generateNonce();
|
|
520
|
+
|
|
521
|
+
expect(typeof nonce).toBe('string');
|
|
522
|
+
expect(nonce.length).toBeGreaterThan(0);
|
|
523
|
+
// Base64 alphabet: A-Z, a-z, 0-9, +, /, =
|
|
524
|
+
expect(nonce).toMatch(/^[A-Za-z0-9+/=]+$/);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('generates different nonces on each call', () => {
|
|
528
|
+
const nonces = new Set<string>();
|
|
529
|
+
for (let i = 0; i < 50; i++) {
|
|
530
|
+
nonces.add(generateNonce());
|
|
531
|
+
}
|
|
532
|
+
// With 128-bit random values, collisions are astronomically unlikely
|
|
533
|
+
expect(nonces.size).toBe(50);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('nonce-based CSP headers', () => {
|
|
538
|
+
const mockEnv = { DB: {} };
|
|
539
|
+
const mockCtx = {} as ExecutionContext;
|
|
540
|
+
|
|
541
|
+
it('CSP header contains nonce (not unsafe-inline) for script-src', async () => {
|
|
542
|
+
const worker = createHandler({
|
|
543
|
+
app: () => mockApp(vi.fn().mockResolvedValue(new Response('OK'))),
|
|
544
|
+
basePath: '/api',
|
|
545
|
+
securityHeaders: true,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const response = await worker.fetch(
|
|
549
|
+
new Request('https://example.com/api/test'),
|
|
550
|
+
mockEnv,
|
|
551
|
+
mockCtx,
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
const csp = response.headers.get('Content-Security-Policy')!;
|
|
555
|
+
expect(csp).toBeTruthy();
|
|
556
|
+
// Extract the script-src directive and verify it uses nonce, not unsafe-inline
|
|
557
|
+
const scriptSrcMatch = csp.match(/script-src\s+([^;]+)/);
|
|
558
|
+
expect(scriptSrcMatch).toBeTruthy();
|
|
559
|
+
const scriptSrc = scriptSrcMatch![1];
|
|
560
|
+
expect(scriptSrc).not.toContain('unsafe-inline');
|
|
561
|
+
expect(scriptSrc).toMatch(/'nonce-[A-Za-z0-9+/=]+'/);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('CSP header keeps unsafe-inline for style-src', async () => {
|
|
565
|
+
const worker = createHandler({
|
|
566
|
+
app: () => mockApp(vi.fn().mockResolvedValue(new Response('OK'))),
|
|
567
|
+
basePath: '/api',
|
|
568
|
+
securityHeaders: true,
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const response = await worker.fetch(
|
|
572
|
+
new Request('https://example.com/api/test'),
|
|
573
|
+
mockEnv,
|
|
574
|
+
mockCtx,
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
const csp = response.headers.get('Content-Security-Policy');
|
|
578
|
+
expect(csp).toMatch(/style-src 'self' 'unsafe-inline'/);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('each request gets a different nonce in the CSP header', async () => {
|
|
582
|
+
const worker = createHandler({
|
|
583
|
+
app: () => mockApp(vi.fn().mockResolvedValue(new Response('OK'))),
|
|
584
|
+
basePath: '/api',
|
|
585
|
+
securityHeaders: true,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const nonces: string[] = [];
|
|
589
|
+
for (let i = 0; i < 10; i++) {
|
|
590
|
+
const response = await worker.fetch(
|
|
591
|
+
new Request('https://example.com/api/test'),
|
|
592
|
+
mockEnv,
|
|
593
|
+
mockCtx,
|
|
594
|
+
);
|
|
595
|
+
const csp = response.headers.get('Content-Security-Policy')!;
|
|
596
|
+
const match = csp.match(/nonce-([A-Za-z0-9+/=]+)/);
|
|
597
|
+
expect(match).toBeTruthy();
|
|
598
|
+
nonces.push(match![1]);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// All nonces should be unique
|
|
602
|
+
expect(new Set(nonces).size).toBe(10);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('applies nonce-based CSP to SSR responses', async () => {
|
|
606
|
+
const worker = createHandler({
|
|
607
|
+
app: () => mockApp(),
|
|
608
|
+
basePath: '/api',
|
|
609
|
+
ssr: () => Promise.resolve(new Response('<html></html>')),
|
|
610
|
+
securityHeaders: true,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
const response = await worker.fetch(new Request('https://example.com/'), mockEnv, mockCtx);
|
|
614
|
+
|
|
615
|
+
const csp = response.headers.get('Content-Security-Policy');
|
|
616
|
+
expect(csp).toBeTruthy();
|
|
617
|
+
expect(csp).toMatch(/script-src 'self' 'nonce-[A-Za-z0-9+/=]+'/);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('applies nonce-based CSP to 404 responses', async () => {
|
|
621
|
+
const worker = createHandler({
|
|
622
|
+
app: () => mockApp(),
|
|
623
|
+
basePath: '/api',
|
|
624
|
+
securityHeaders: true,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const response = await worker.fetch(
|
|
628
|
+
new Request('https://example.com/some-page'),
|
|
629
|
+
mockEnv,
|
|
630
|
+
mockCtx,
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
expect(response.status).toBe(404);
|
|
634
|
+
const csp = response.headers.get('Content-Security-Policy');
|
|
635
|
+
expect(csp).toBeTruthy();
|
|
636
|
+
expect(csp).toMatch(/script-src 'self' 'nonce-[A-Za-z0-9+/=]+'/);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('applies nonce-based CSP to 500 error responses', async () => {
|
|
640
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
641
|
+
|
|
642
|
+
const worker = createHandler({
|
|
643
|
+
app: () => mockApp(vi.fn().mockRejectedValue(new Error('fail'))),
|
|
644
|
+
basePath: '/api',
|
|
645
|
+
securityHeaders: true,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const response = await worker.fetch(
|
|
649
|
+
new Request('https://example.com/api/test'),
|
|
650
|
+
mockEnv,
|
|
651
|
+
mockCtx,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
expect(response.status).toBe(500);
|
|
655
|
+
const csp = response.headers.get('Content-Security-Policy');
|
|
656
|
+
expect(csp).toMatch(/script-src 'self' 'nonce-[A-Za-z0-9+/=]+'/);
|
|
657
|
+
|
|
658
|
+
consoleErrorSpy.mockRestore();
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
describe('generateHTMLTemplate with nonce', () => {
|
|
663
|
+
it('adds nonce attribute to script tag when nonce is provided', () => {
|
|
664
|
+
const html = generateHTMLTemplate('/assets/client.js', 'My App', 'abc123');
|
|
665
|
+
|
|
666
|
+
expect(html).toContain('<script type="module" src="/assets/client.js" nonce="abc123"></script>');
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('omits nonce attribute when nonce is not provided', () => {
|
|
670
|
+
const html = generateHTMLTemplate('/assets/client.js', 'My App');
|
|
671
|
+
|
|
672
|
+
expect(html).toContain('<script type="module" src="/assets/client.js"></script>');
|
|
673
|
+
expect(html).not.toContain('nonce');
|
|
674
|
+
});
|
|
675
|
+
});
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
$ vitest run
|
|
2
|
-
|
|
3
|
-
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/viniciusdacal/vertz-dev/vertz/.claude/worktrees/poc-ssr-hmr/packages/cloudflare[39m
|
|
4
|
-
|
|
5
|
-
[32m✓[39m tests/handler.test.ts [2m([22m[2m23 tests[22m[2m)[22m[32m 72[2mms[22m[39m
|
|
6
|
-
|
|
7
|
-
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
8
|
-
[2m Tests [22m [1m[32m23 passed[39m[22m[90m (23)[39m
|
|
9
|
-
[2m Start at [22m 23:53:54
|
|
10
|
-
[2m Duration [22m 397ms[2m (transform 146ms, setup 0ms, import 186ms, tests 72ms, environment 0ms)[22m
|
|
11
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|
package/bunup.config.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'bunup';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
entry: ['src/index.ts'],
|
|
5
|
-
format: ['esm'],
|
|
6
|
-
dts: { inferTypes: true },
|
|
7
|
-
clean: true,
|
|
8
|
-
external: ['@vertz/core', '@vertz/ui-server'],
|
|
9
|
-
// onSuccess strips the CJS createRequire shim that Bun.build injects.
|
|
10
|
-
// The shim references import.meta.url which is undefined on Cloudflare
|
|
11
|
-
// Workers, and __require is never actually called in the output.
|
|
12
|
-
onSuccess: async () => {
|
|
13
|
-
const path = 'dist/index.js';
|
|
14
|
-
const code = await Bun.file(path).text();
|
|
15
|
-
const cleaned = code
|
|
16
|
-
.replace(/import \{ createRequire \} from "node:module";\n?/, '')
|
|
17
|
-
.replace(/var __require = \/\* @__PURE__ \*\/ createRequire\(import\.meta\.url\);\n?/, '');
|
|
18
|
-
await Bun.write(path, cleaned);
|
|
19
|
-
},
|
|
20
|
-
});
|