veryfront 0.1.38 → 0.1.39
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 +3 -2
- package/esm/src/html/dev-scripts.d.ts.map +1 -1
- package/esm/src/html/dev-scripts.js +13 -7
- package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-html-generator.js +10 -5
- package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts +23 -0
- package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/studio/bridge-modules.handler.js +100 -0
- package/esm/src/server/runtime-handler/index.js +2 -2
- package/package.json +1 -1
- package/src/deno.js +3 -2
- package/src/src/html/dev-scripts.ts +14 -7
- package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -10
- package/src/src/server/handlers/studio/bridge-modules.handler.ts +126 -0
- package/src/src/server/runtime-handler/index.ts +2 -2
- package/esm/src/server/handlers/studio/endpoints.handler.d.ts +0 -12
- package/esm/src/server/handlers/studio/endpoints.handler.d.ts.map +0 -1
- package/esm/src/server/handlers/studio/endpoints.handler.js +0 -29
- package/esm/src/studio/bridge-template.d.ts +0 -11
- package/esm/src/studio/bridge-template.d.ts.map +0 -1
- package/esm/src/studio/bridge-template.js +0 -4794
- package/src/src/server/handlers/studio/endpoints.handler.ts +0 -49
- package/src/src/studio/bridge-template.ts +0 -4804
package/esm/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"nodeModulesDir": "auto",
|
|
6
6
|
"exclude": [
|
|
@@ -352,7 +352,8 @@ export default {
|
|
|
352
352
|
],
|
|
353
353
|
"exclude": [
|
|
354
354
|
"dist/",
|
|
355
|
-
"coverage/"
|
|
355
|
+
"coverage/",
|
|
356
|
+
"src/studio/bridge/"
|
|
356
357
|
],
|
|
357
358
|
"rules": {
|
|
358
359
|
"tags": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-scripts.d.ts","sourceRoot":"","sources":["../../../src/src/html/dev-scripts.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA+BnD;AAMD,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"dev-scripts.d.ts","sourceRoot":"","sources":["../../../src/src/html/dev-scripts.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA+BnD;AAMD,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAwBrE"}
|
|
@@ -47,16 +47,22 @@ export function getProdScripts(slug, nonce) {
|
|
|
47
47
|
}
|
|
48
48
|
export function getStudioScripts(options) {
|
|
49
49
|
const nonceAttr = getNonceAttr(options.nonce);
|
|
50
|
-
const
|
|
50
|
+
const bridgeConfig = {
|
|
51
51
|
projectId: options.projectId,
|
|
52
52
|
pageId: options.pageId,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
pagePath: options.pagePath ?? options.pageId,
|
|
54
|
+
};
|
|
55
|
+
if (options.wsUrl)
|
|
56
|
+
bridgeConfig.wsUrl = options.wsUrl;
|
|
57
|
+
if (options.yjsGuid)
|
|
58
|
+
bridgeConfig.yjsGuid = options.yjsGuid;
|
|
57
59
|
const sourceHashScript = options.sourceHash
|
|
58
|
-
? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__
|
|
60
|
+
? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__=${JSON.stringify(options.sourceHash).replace(/</g, "\\u003c")};</script>\n `
|
|
59
61
|
: "";
|
|
62
|
+
// Escape </script> sequences to prevent XSS breakout from inline JSON
|
|
63
|
+
const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
|
|
64
|
+
const configScript = `<script${nonceAttr}>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>`;
|
|
60
65
|
return `
|
|
61
|
-
${sourceHashScript}
|
|
66
|
+
${sourceHashScript}${configScript}
|
|
67
|
+
<script type="module" src="/_veryfront/studio-bridge.js"${nonceAttr}></script>`;
|
|
62
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAKrD,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,gDAAgD;IAChD,GAAG,EAAE,GAAG,CAAC;IACT,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA0ED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA2FzE"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
|
|
2
1
|
import { escapeHtml } from "../../../utils/html-escape.js";
|
|
3
2
|
/**
|
|
4
3
|
* Detect the preferred color theme from request parameters and client hints.
|
|
@@ -49,13 +48,19 @@ function buildStudioScript(url, projectId, filePath, branchId, apiBaseUrl) {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
const yjsGuid = branchId ? `${canonicalProjectId}:${branchId}` : canonicalProjectId;
|
|
52
|
-
|
|
51
|
+
const bridgeConfig = {
|
|
53
52
|
projectId: canonicalProjectId,
|
|
54
53
|
pageId: canonicalPageId,
|
|
55
54
|
pagePath: filePath,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
};
|
|
56
|
+
if (wsUrl)
|
|
57
|
+
bridgeConfig.wsUrl = wsUrl;
|
|
58
|
+
if (yjsGuid)
|
|
59
|
+
bridgeConfig.yjsGuid = yjsGuid;
|
|
60
|
+
// Escape </script> sequences to prevent XSS breakout from inline JSON
|
|
61
|
+
const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
|
|
62
|
+
return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
|
|
63
|
+
<script type="module" src="/_veryfront/studio-bridge.js"></script>`;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Generate a complete HTML document for markdown preview rendering.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Bridge Handler
|
|
3
|
+
*
|
|
4
|
+
* Bundles the decomposed bridge TypeScript modules into a single JS file
|
|
5
|
+
* using esbuild (same JIT pipeline as DevFileHandler).
|
|
6
|
+
*
|
|
7
|
+
* Route: /_veryfront/studio-bridge.js
|
|
8
|
+
*
|
|
9
|
+
* @module server/handlers/studio/bridge-modules
|
|
10
|
+
*/
|
|
11
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
12
|
+
import { BaseHandler } from "../../../security/index.js";
|
|
13
|
+
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
14
|
+
export declare class StudioBridgeModulesHandler extends BaseHandler {
|
|
15
|
+
metadata: HandlerMetadata;
|
|
16
|
+
handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Invalidate the bundle cache.
|
|
20
|
+
* Used by dev file watchers to bust the cache on source changes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function invalidateBridgeModuleCache(): void;
|
|
23
|
+
//# sourceMappingURL=bridge-modules.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-modules.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/studio/bridge-modules.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,aAAa,CAAC;AAkDrB,qBAAa,0BAA2B,SAAQ,WAAW;IACzD,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAwChF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Bridge Handler
|
|
3
|
+
*
|
|
4
|
+
* Bundles the decomposed bridge TypeScript modules into a single JS file
|
|
5
|
+
* using esbuild (same JIT pipeline as DevFileHandler).
|
|
6
|
+
*
|
|
7
|
+
* Route: /_veryfront/studio-bridge.js
|
|
8
|
+
*
|
|
9
|
+
* @module server/handlers/studio/bridge-modules
|
|
10
|
+
*/
|
|
11
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
12
|
+
import { BaseHandler } from "../../../security/index.js";
|
|
13
|
+
import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
|
|
14
|
+
/** Cached bundle output. */
|
|
15
|
+
let bundleCache = null;
|
|
16
|
+
/** Resolve the bridge source directory from this module's location. */
|
|
17
|
+
const BRIDGE_DIR = new URL("../../../studio/bridge/", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
|
|
18
|
+
/**
|
|
19
|
+
* Compute a content hash for ETag.
|
|
20
|
+
*/
|
|
21
|
+
async function computeEtag(content) {
|
|
22
|
+
const data = new TextEncoder().encode(content);
|
|
23
|
+
const hashBuffer = await dntShim.crypto.subtle.digest("SHA-256", data);
|
|
24
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
25
|
+
return hashArray.slice(0, 16).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Bundle the bridge coordinator (and all its imports) into a single JS file.
|
|
29
|
+
* Uses esbuild.build() with bundle:true — same approach as DevFileHandler.
|
|
30
|
+
*/
|
|
31
|
+
async function bundleBridge() {
|
|
32
|
+
if (bundleCache)
|
|
33
|
+
return bundleCache;
|
|
34
|
+
const entryPoint = `${BRIDGE_DIR}bridge-coordinator.ts`;
|
|
35
|
+
const source = await dntShim.Deno.readTextFile(entryPoint);
|
|
36
|
+
const { build } = await import("esbuild");
|
|
37
|
+
const { outputFiles } = await build({
|
|
38
|
+
bundle: true,
|
|
39
|
+
write: false,
|
|
40
|
+
format: "esm",
|
|
41
|
+
platform: "browser",
|
|
42
|
+
target: "es2022",
|
|
43
|
+
stdin: {
|
|
44
|
+
contents: source,
|
|
45
|
+
loader: "ts",
|
|
46
|
+
resolveDir: BRIDGE_DIR,
|
|
47
|
+
sourcefile: entryPoint,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const js = outputFiles?.[0]?.text ?? "";
|
|
51
|
+
const etag = await computeEtag(js);
|
|
52
|
+
bundleCache = { js, etag };
|
|
53
|
+
return bundleCache;
|
|
54
|
+
}
|
|
55
|
+
export class StudioBridgeModulesHandler extends BaseHandler {
|
|
56
|
+
metadata = {
|
|
57
|
+
name: "StudioBridgeModulesHandler",
|
|
58
|
+
priority: PRIORITY_HIGH_DEV,
|
|
59
|
+
patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
|
|
60
|
+
enabled: () => true,
|
|
61
|
+
};
|
|
62
|
+
async handle(req, ctx) {
|
|
63
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
64
|
+
return this.continue();
|
|
65
|
+
}
|
|
66
|
+
const ifNoneMatch = req.headers.get("if-none-match");
|
|
67
|
+
try {
|
|
68
|
+
const { js, etag } = await bundleBridge();
|
|
69
|
+
if (ifNoneMatch === `"${etag}"`) {
|
|
70
|
+
return this.respond(new dntShim.Response(null, {
|
|
71
|
+
status: 304,
|
|
72
|
+
headers: { ETag: `"${etag}"`, "Cache-Control": "no-cache" },
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
return this.respond(new dntShim.Response(js, {
|
|
76
|
+
status: HTTP_OK,
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
79
|
+
"Cache-Control": "no-cache",
|
|
80
|
+
ETag: `"${etag}"`,
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error("[StudioBridgeHandler] Bundle error:", message);
|
|
87
|
+
return this.respond(new dntShim.Response(`// Bundle error: ${message}`, {
|
|
88
|
+
status: 500,
|
|
89
|
+
headers: { "Content-Type": "application/javascript; charset=utf-8" },
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Invalidate the bundle cache.
|
|
96
|
+
* Used by dev file watchers to bust the cache on source changes.
|
|
97
|
+
*/
|
|
98
|
+
export function invalidateBridgeModuleCache() {
|
|
99
|
+
bundleCache = null;
|
|
100
|
+
}
|
|
@@ -28,7 +28,7 @@ import { DevEndpointsHandler } from "../handlers/dev/endpoints.handler.js";
|
|
|
28
28
|
import { DevFileHandler } from "../handlers/dev/files/index.js";
|
|
29
29
|
import { DebugContextHandler } from "../handlers/dev/debug-context.handler.js";
|
|
30
30
|
import { StylesCSSHandler } from "../handlers/dev/styles-css.handler.js";
|
|
31
|
-
import {
|
|
31
|
+
import { StudioBridgeModulesHandler } from "../handlers/studio/bridge-modules.handler.js";
|
|
32
32
|
import { StaticHandler } from "../handlers/request/static.handler.js";
|
|
33
33
|
import { SnippetHandler } from "../handlers/request/snippet.handler.js";
|
|
34
34
|
import { LibModulesHandler } from "../handlers/request/lib-modules.handler.js";
|
|
@@ -123,7 +123,7 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
|
|
|
123
123
|
new OpenAPIDocsHandler(),
|
|
124
124
|
new DevDashboardHandler(),
|
|
125
125
|
new ProjectsHandler(),
|
|
126
|
-
new
|
|
126
|
+
new StudioBridgeModulesHandler(),
|
|
127
127
|
new CSSHandler(),
|
|
128
128
|
new DevFileHandler(),
|
|
129
129
|
new SnippetHandler(),
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"nodeModulesDir": "auto",
|
|
6
6
|
"exclude": [
|
|
@@ -352,7 +352,8 @@ export default {
|
|
|
352
352
|
],
|
|
353
353
|
"exclude": [
|
|
354
354
|
"dist/",
|
|
355
|
-
"coverage/"
|
|
355
|
+
"coverage/",
|
|
356
|
+
"src/studio/bridge/"
|
|
356
357
|
],
|
|
357
358
|
"rules": {
|
|
358
359
|
"tags": [
|
|
@@ -68,18 +68,25 @@ export interface StudioScriptOptions {
|
|
|
68
68
|
export function getStudioScripts(options: StudioScriptOptions): string {
|
|
69
69
|
const nonceAttr = getNonceAttr(options.nonce);
|
|
70
70
|
|
|
71
|
-
const
|
|
71
|
+
const bridgeConfig: Record<string, unknown> = {
|
|
72
72
|
projectId: options.projectId,
|
|
73
73
|
pageId: options.pageId,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
pagePath: options.pagePath ?? options.pageId,
|
|
75
|
+
};
|
|
76
|
+
if (options.wsUrl) bridgeConfig.wsUrl = options.wsUrl;
|
|
77
|
+
if (options.yjsGuid) bridgeConfig.yjsGuid = options.yjsGuid;
|
|
78
78
|
|
|
79
79
|
const sourceHashScript = options.sourceHash
|
|
80
|
-
? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__
|
|
80
|
+
? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__=${
|
|
81
|
+
JSON.stringify(options.sourceHash).replace(/</g, "\\u003c")
|
|
82
|
+
};</script>\n `
|
|
81
83
|
: "";
|
|
82
84
|
|
|
85
|
+
// Escape </script> sequences to prevent XSS breakout from inline JSON
|
|
86
|
+
const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
|
|
87
|
+
const configScript = `<script${nonceAttr}>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>`;
|
|
88
|
+
|
|
83
89
|
return `
|
|
84
|
-
${sourceHashScript}
|
|
90
|
+
${sourceHashScript}${configScript}
|
|
91
|
+
<script type="module" src="/_veryfront/studio-bridge.js"${nonceAttr}></script>`;
|
|
85
92
|
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import * as dntShim from "../../../../_dnt.shims.js";
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
|
|
14
13
|
import { escapeHtml } from "../../../utils/html-escape.js";
|
|
15
14
|
|
|
16
15
|
/** Options for generating markdown preview HTML. */
|
|
@@ -93,15 +92,18 @@ function buildStudioScript(
|
|
|
93
92
|
}
|
|
94
93
|
const yjsGuid = branchId ? `${canonicalProjectId}:${branchId}` : canonicalProjectId;
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
const bridgeConfig: Record<string, unknown> = {
|
|
96
|
+
projectId: canonicalProjectId,
|
|
97
|
+
pageId: canonicalPageId,
|
|
98
|
+
pagePath: filePath,
|
|
99
|
+
};
|
|
100
|
+
if (wsUrl) bridgeConfig.wsUrl = wsUrl;
|
|
101
|
+
if (yjsGuid) bridgeConfig.yjsGuid = yjsGuid;
|
|
102
|
+
|
|
103
|
+
// Escape </script> sequences to prevent XSS breakout from inline JSON
|
|
104
|
+
const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
|
|
105
|
+
return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
|
|
106
|
+
<script type="module" src="/_veryfront/studio-bridge.js"></script>`;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
/**
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Bridge Handler
|
|
3
|
+
*
|
|
4
|
+
* Bundles the decomposed bridge TypeScript modules into a single JS file
|
|
5
|
+
* using esbuild (same JIT pipeline as DevFileHandler).
|
|
6
|
+
*
|
|
7
|
+
* Route: /_veryfront/studio-bridge.js
|
|
8
|
+
*
|
|
9
|
+
* @module server/handlers/studio/bridge-modules
|
|
10
|
+
*/
|
|
11
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import { BaseHandler } from "../../../security/index.js";
|
|
15
|
+
import type {
|
|
16
|
+
HandlerContext,
|
|
17
|
+
HandlerMetadata,
|
|
18
|
+
HandlerPriority,
|
|
19
|
+
HandlerResult,
|
|
20
|
+
} from "../types.js";
|
|
21
|
+
import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
|
|
22
|
+
|
|
23
|
+
/** Cached bundle output. */
|
|
24
|
+
let bundleCache: { js: string; etag: string } | null = null;
|
|
25
|
+
|
|
26
|
+
/** Resolve the bridge source directory from this module's location. */
|
|
27
|
+
const BRIDGE_DIR = new URL("../../../studio/bridge/", import.meta.url).pathname;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compute a content hash for ETag.
|
|
31
|
+
*/
|
|
32
|
+
async function computeEtag(content: string): Promise<string> {
|
|
33
|
+
const data = new TextEncoder().encode(content);
|
|
34
|
+
const hashBuffer = await dntShim.crypto.subtle.digest("SHA-256", data);
|
|
35
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
36
|
+
return hashArray.slice(0, 16).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Bundle the bridge coordinator (and all its imports) into a single JS file.
|
|
41
|
+
* Uses esbuild.build() with bundle:true — same approach as DevFileHandler.
|
|
42
|
+
*/
|
|
43
|
+
async function bundleBridge(): Promise<{ js: string; etag: string }> {
|
|
44
|
+
if (bundleCache) return bundleCache;
|
|
45
|
+
|
|
46
|
+
const entryPoint = `${BRIDGE_DIR}bridge-coordinator.ts`;
|
|
47
|
+
const source = await dntShim.Deno.readTextFile(entryPoint);
|
|
48
|
+
|
|
49
|
+
const { build } = await import("esbuild");
|
|
50
|
+
const { outputFiles } = await build({
|
|
51
|
+
bundle: true,
|
|
52
|
+
write: false,
|
|
53
|
+
format: "esm",
|
|
54
|
+
platform: "browser",
|
|
55
|
+
target: "es2022",
|
|
56
|
+
stdin: {
|
|
57
|
+
contents: source,
|
|
58
|
+
loader: "ts",
|
|
59
|
+
resolveDir: BRIDGE_DIR,
|
|
60
|
+
sourcefile: entryPoint,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const js = outputFiles?.[0]?.text ?? "";
|
|
65
|
+
const etag = await computeEtag(js);
|
|
66
|
+
bundleCache = { js, etag };
|
|
67
|
+
return bundleCache;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class StudioBridgeModulesHandler extends BaseHandler {
|
|
71
|
+
metadata: HandlerMetadata = {
|
|
72
|
+
name: "StudioBridgeModulesHandler",
|
|
73
|
+
priority: PRIORITY_HIGH_DEV as HandlerPriority,
|
|
74
|
+
patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
|
|
75
|
+
enabled: () => true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
async handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult> {
|
|
79
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
80
|
+
return this.continue();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ifNoneMatch = req.headers.get("if-none-match");
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const { js, etag } = await bundleBridge();
|
|
87
|
+
|
|
88
|
+
if (ifNoneMatch === `"${etag}"`) {
|
|
89
|
+
return this.respond(
|
|
90
|
+
new dntShim.Response(null, {
|
|
91
|
+
status: 304,
|
|
92
|
+
headers: { ETag: `"${etag}"`, "Cache-Control": "no-cache" },
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.respond(
|
|
98
|
+
new dntShim.Response(js, {
|
|
99
|
+
status: HTTP_OK,
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
102
|
+
"Cache-Control": "no-cache",
|
|
103
|
+
ETag: `"${etag}"`,
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
console.error("[StudioBridgeHandler] Bundle error:", message);
|
|
110
|
+
return this.respond(
|
|
111
|
+
new dntShim.Response(`// Bundle error: ${message}`, {
|
|
112
|
+
status: 500,
|
|
113
|
+
headers: { "Content-Type": "application/javascript; charset=utf-8" },
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Invalidate the bundle cache.
|
|
122
|
+
* Used by dev file watchers to bust the cache on source changes.
|
|
123
|
+
*/
|
|
124
|
+
export function invalidateBridgeModuleCache(): void {
|
|
125
|
+
bundleCache = null;
|
|
126
|
+
}
|
|
@@ -39,7 +39,7 @@ import { DevEndpointsHandler } from "../handlers/dev/endpoints.handler.js";
|
|
|
39
39
|
import { DevFileHandler } from "../handlers/dev/files/index.js";
|
|
40
40
|
import { DebugContextHandler } from "../handlers/dev/debug-context.handler.js";
|
|
41
41
|
import { StylesCSSHandler } from "../handlers/dev/styles-css.handler.js";
|
|
42
|
-
import {
|
|
42
|
+
import { StudioBridgeModulesHandler } from "../handlers/studio/bridge-modules.handler.js";
|
|
43
43
|
import { StaticHandler } from "../handlers/request/static.handler.js";
|
|
44
44
|
import { SnippetHandler } from "../handlers/request/snippet.handler.js";
|
|
45
45
|
import { LibModulesHandler } from "../handlers/request/lib-modules.handler.js";
|
|
@@ -206,7 +206,7 @@ export function createVeryfrontHandler(
|
|
|
206
206
|
new OpenAPIDocsHandler(),
|
|
207
207
|
new DevDashboardHandler(),
|
|
208
208
|
new ProjectsHandler(),
|
|
209
|
-
new
|
|
209
|
+
new StudioBridgeModulesHandler(),
|
|
210
210
|
new CSSHandler(),
|
|
211
211
|
new DevFileHandler(),
|
|
212
212
|
new SnippetHandler(),
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Studio Endpoints Handler
|
|
3
|
-
* Handles studio bridge script and other studio-specific endpoints
|
|
4
|
-
*/
|
|
5
|
-
import * as dntShim from "../../../../_dnt.shims.js";
|
|
6
|
-
import { BaseHandler } from "../../../security/index.js";
|
|
7
|
-
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
8
|
-
export declare class StudioEndpointsHandler extends BaseHandler {
|
|
9
|
-
metadata: HandlerMetadata;
|
|
10
|
-
handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=endpoints.handler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"endpoints.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/studio/endpoints.handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,aAAa,CAAC;AAIrB,qBAAa,sBAAuB,SAAQ,WAAW;IACrD,QAAQ,EAAE,eAAe,CAKvB;IAEF,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAuB1E"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { BaseHandler } from "../../../security/index.js";
|
|
2
|
-
import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
|
|
3
|
-
import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
|
|
4
|
-
export class StudioEndpointsHandler extends BaseHandler {
|
|
5
|
-
metadata = {
|
|
6
|
-
name: "StudioEndpointsHandler",
|
|
7
|
-
priority: PRIORITY_HIGH_DEV,
|
|
8
|
-
patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
|
|
9
|
-
enabled: () => true, // Always enabled - studio_embed check is in the script
|
|
10
|
-
};
|
|
11
|
-
handle(req, ctx) {
|
|
12
|
-
if (!this.shouldHandle(req, ctx)) {
|
|
13
|
-
return Promise.resolve(this.continue());
|
|
14
|
-
}
|
|
15
|
-
const url = new URL(req.url);
|
|
16
|
-
if (url.pathname !== "/_veryfront/studio-bridge.js") {
|
|
17
|
-
return Promise.resolve(this.continue());
|
|
18
|
-
}
|
|
19
|
-
const builder = this.createResponseBuilder(ctx);
|
|
20
|
-
const projectId = url.searchParams.get("projectId") ?? "";
|
|
21
|
-
const pageId = url.searchParams.get("pageId") ?? "";
|
|
22
|
-
const pagePath = url.searchParams.get("pagePath") ?? undefined;
|
|
23
|
-
const wsUrl = url.searchParams.get("wsUrl") ?? undefined;
|
|
24
|
-
const yjsGuid = url.searchParams.get("yjsGuid") ?? undefined;
|
|
25
|
-
const script = generateStudioBridgeScript({ projectId, pageId, pagePath, wsUrl, yjsGuid });
|
|
26
|
-
const response = builder.withCache("no-cache").javascript(script, HTTP_OK);
|
|
27
|
-
return Promise.resolve(this.respond(response));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface StudioBridgeOptions {
|
|
2
|
-
projectId: string;
|
|
3
|
-
pageId: string;
|
|
4
|
-
pagePath?: string;
|
|
5
|
-
wsUrl?: string;
|
|
6
|
-
yjsGuid?: string;
|
|
7
|
-
debugSkipInit?: boolean;
|
|
8
|
-
debugExposeInternals?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export declare function generateStudioBridgeScript(options: StudioBridgeOptions): string;
|
|
11
|
-
//# sourceMappingURL=bridge-template.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAyrJ/E"}
|