@editframe/dev-server 0.51.0
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/LICENSE-FULL.md +148 -0
- package/LICENSE.md +58 -0
- package/dist/efHandlers.d.ts +20 -0
- package/dist/efHandlers.js +272 -0
- package/dist/efHandlers.js.map +1 -0
- package/dist/forbidRelativePaths.d.ts +7 -0
- package/dist/forbidRelativePaths.js +8 -0
- package/dist/forbidRelativePaths.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jitTranscodeMiddleware.d.ts +126 -0
- package/dist/jitTranscodeMiddleware.js +421 -0
- package/dist/jitTranscodeMiddleware.js.map +1 -0
- package/dist/middleware.d.ts +25 -0
- package/dist/middleware.js +156 -0
- package/dist/middleware.js.map +1 -0
- package/dist/router.d.ts +31 -0
- package/dist/router.js +80 -0
- package/dist/router.js.map +1 -0
- package/dist/sendTaskResult.d.ts +8 -0
- package/dist/sendTaskResult.js +67 -0
- package/dist/sendTaskResult.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.js +26 -0
- package/dist/server.js.map +1 -0
- package/package.json +47 -0
- package/tsdown.config.ts +9 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { forbidRelativePaths } from "./forbidRelativePaths.js";
|
|
2
|
+
import { sendTaskResult } from "./sendTaskResult.js";
|
|
3
|
+
import debug from "debug";
|
|
4
|
+
import path, { join } from "node:path";
|
|
5
|
+
import { rm } from "node:fs/promises";
|
|
6
|
+
//#region src/middleware.ts
|
|
7
|
+
function createAssetsApiMiddleware(options, deps) {
|
|
8
|
+
const { cacheImage, findOrCreateCaptions } = deps;
|
|
9
|
+
return async (req, res, next) => {
|
|
10
|
+
const log = debug("ef:dev-server:assets");
|
|
11
|
+
const reqUrl = req.url || "";
|
|
12
|
+
if (!reqUrl.startsWith("/api/v1/assets/")) return next();
|
|
13
|
+
forbidRelativePaths(req);
|
|
14
|
+
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
15
|
+
const urlPath = url.pathname;
|
|
16
|
+
const src = url.searchParams.get("src");
|
|
17
|
+
if (!src) {
|
|
18
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
19
|
+
res.end(JSON.stringify({ error: "src parameter is required" }));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const isRemote = src.startsWith("http://") || src.startsWith("https://");
|
|
23
|
+
const absolutePath = isRemote ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
24
|
+
log(`Handling assets API: ${urlPath} src=${src}`);
|
|
25
|
+
try {
|
|
26
|
+
if (urlPath === "/api/v1/assets/image") {
|
|
27
|
+
if (isRemote) {
|
|
28
|
+
const response = await fetch(src);
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
res.writeHead(response.status);
|
|
31
|
+
res.end();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
35
|
+
const buffer = await response.arrayBuffer();
|
|
36
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
37
|
+
res.end(Buffer.from(buffer));
|
|
38
|
+
} else sendTaskResult(req, res, await cacheImage(options.cacheRoot, absolutePath));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (urlPath === "/api/v1/assets/captions") {
|
|
42
|
+
sendTaskResult(req, res, await findOrCreateCaptions(options.cacheRoot, absolutePath));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
46
|
+
res.end(JSON.stringify({ error: "Unknown assets endpoint" }));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
log(`Error handling assets request: ${error}`);
|
|
49
|
+
if (error.code === "ENOENT") {
|
|
50
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
51
|
+
res.end("File not found");
|
|
52
|
+
} else {
|
|
53
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
54
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function createLocalFilesApiMiddleware(options, deps) {
|
|
60
|
+
const { generateTrack, generateScrubTrack, generateTrackFragmentIndex, md5FilePath } = deps;
|
|
61
|
+
return async (req, res, next) => {
|
|
62
|
+
const log = debug("ef:dev-server:files");
|
|
63
|
+
const reqUrl = req.url || "";
|
|
64
|
+
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
65
|
+
const urlPath = url.pathname;
|
|
66
|
+
const src = url.searchParams.get("src");
|
|
67
|
+
if (!src || urlPath !== "/api/v1/files/index" && urlPath !== "/api/v1/files/md5" && urlPath !== "/api/v1/files/track") return next();
|
|
68
|
+
forbidRelativePaths(req);
|
|
69
|
+
const absolutePath = src.startsWith("http") ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
70
|
+
log(`Handling local file API: ${urlPath} for ${absolutePath}`);
|
|
71
|
+
try {
|
|
72
|
+
if (urlPath === "/api/v1/files/index") {
|
|
73
|
+
log(`Serving track fragment index for ${absolutePath}`);
|
|
74
|
+
sendTaskResult(req, res, await generateTrackFragmentIndex(options.cacheRoot, absolutePath));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (urlPath === "/api/v1/files/md5") {
|
|
78
|
+
log(`Getting MD5 for ${absolutePath}`);
|
|
79
|
+
try {
|
|
80
|
+
const md5 = await md5FilePath(absolutePath);
|
|
81
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
82
|
+
res.end(JSON.stringify({ md5 }));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error.code === "ENOENT") {
|
|
85
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
86
|
+
res.end("File not found");
|
|
87
|
+
} else {
|
|
88
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
89
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (urlPath === "/api/v1/files/track") {
|
|
95
|
+
const trackIdStr = url.searchParams.get("trackId");
|
|
96
|
+
const segmentIdStr = url.searchParams.get("segmentId");
|
|
97
|
+
if (!trackIdStr) {
|
|
98
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
99
|
+
res.end(JSON.stringify({ error: "trackId parameter is required" }));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const trackId = parseInt(trackIdStr, 10);
|
|
103
|
+
if (trackId === -1) {
|
|
104
|
+
log(`Serving scrub track for ${absolutePath}`);
|
|
105
|
+
sendTaskResult(req, res, await generateScrubTrack(options.cacheRoot, absolutePath));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
log(`Serving track ${trackId} segment ${segmentIdStr || "all"} for ${absolutePath}`);
|
|
109
|
+
const trackUrl = `/@ef-track/${src}?trackId=${trackId}${segmentIdStr ? `&segmentId=${segmentIdStr}` : ""}`;
|
|
110
|
+
sendTaskResult(req, res, await generateTrack(options.cacheRoot, absolutePath, trackUrl));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
log(`Error handling local file request: ${error}`);
|
|
115
|
+
if (error.code === "ENOENT") {
|
|
116
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
117
|
+
res.end("File not found");
|
|
118
|
+
} else {
|
|
119
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
120
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function handleClearCache(req, res, cacheRoot) {
|
|
126
|
+
const log = debug("ef:dev-server");
|
|
127
|
+
if (req.method !== "DELETE") {
|
|
128
|
+
res.writeHead(405, { Allow: "DELETE" });
|
|
129
|
+
res.end();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
log(`Clearing cache for ${cacheRoot}`);
|
|
133
|
+
const cachePath = join(cacheRoot, ".cache");
|
|
134
|
+
const maxRetries = 3;
|
|
135
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
136
|
+
await rm(cachePath, {
|
|
137
|
+
recursive: true,
|
|
138
|
+
force: true
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error.code === "ENOENT") break;
|
|
143
|
+
if (error.code === "ENOTEMPTY" && attempt < maxRetries - 1) {
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
log(`Warning: Cache clear attempt ${attempt + 1} failed: ${error.message}`);
|
|
148
|
+
if (attempt === maxRetries - 1) log(`Cache clear failed after ${maxRetries} attempts, continuing anyway`);
|
|
149
|
+
}
|
|
150
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
151
|
+
res.end("Cache cleared");
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { createAssetsApiMiddleware, createLocalFilesApiMiddleware, handleClearCache };
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"mappings":";;;;;;AA4BA,SAAgB,0BAA0B,SAAwB,MAA8B;CAC9F,MAAM,EAAE,YAAY,yBAAyB;AAC7C,QAAO,OAAO,KAAK,KAAK,SAAS;EAC/B,MAAM,MAAM,MAAM,uBAAuB;EACzC,MAAM,SAAS,IAAI,OAAO;AAE1B,MAAI,CAAC,OAAO,WAAW,kBAAkB,CACvC,QAAO,MAAM;AAGf,sBAAoB,IAAI;EAExB,MAAM,MAAM,IAAI,IAAI,QAAQ,UAAU,IAAI,QAAQ,OAAO;EACzD,MAAM,UAAU,IAAI;EACpB,MAAM,MAAM,IAAI,aAAa,IAAI,MAAM;AAEvC,MAAI,CAAC,KAAK;AACR,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,6BAA6B,CAAC,CAAC;AAC/D;;EAGF,MAAM,WAAW,IAAI,WAAW,UAAU,IAAI,IAAI,WAAW,WAAW;EACxE,MAAM,eAAe,WAAW,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC,QAAQ,SAAS,OAAO;AAE3F,MAAI,wBAAwB,QAAQ,OAAO,MAAM;AAEjD,MAAI;AACF,OAAI,YAAY,wBAAwB;AACtC,QAAI,UAAU;KACZ,MAAM,WAAW,MAAM,MAAM,IAAI;AACjC,SAAI,CAAC,SAAS,IAAI;AAChB,UAAI,UAAU,SAAS,OAAO;AAC9B,UAAI,KAAK;AACT;;KAEF,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;KAC5D,MAAM,SAAS,MAAM,SAAS,aAAa;AAC3C,SAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,SAAI,IAAI,OAAO,KAAK,OAAO,CAAC;UAG5B,gBAAe,KAAK,KADD,MAAM,WAAW,QAAQ,WAAW,aAAa,CAChC;AAEtC;;AAGF,OAAI,YAAY,2BAA2B;AAEzC,mBAAe,KAAK,KADD,MAAM,qBAAqB,QAAQ,WAAW,aAAa,CAC1C;AACpC;;AAGF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,2BAA2B,CAAC,CAAC;WACtD,OAAO;AACd,OAAI,kCAAkC,QAAQ;AAC9C,OAAK,MAAgC,SAAS,UAAU;AACtD,QAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,QAAI,IAAI,iBAAiB;UACpB;AACL,QAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,QAAI,IAAI,KAAK,UAAU,EAAE,OAAQ,MAAgB,SAAS,CAAC,CAAC;;;;;AAMpE,SAAgB,8BAA8B,SAAwB,MAA6B;CACjG,MAAM,EAAE,eAAe,oBAAoB,4BAA4B,gBAAgB;AACvF,QAAO,OAAO,KAAK,KAAK,SAAS;EAC/B,MAAM,MAAM,MAAM,sBAAsB;EACxC,MAAM,SAAS,IAAI,OAAO;EAE1B,MAAM,MAAM,IAAI,IAAI,QAAQ,UAAU,IAAI,QAAQ,OAAO;EACzD,MAAM,UAAU,IAAI;EACpB,MAAM,MAAM,IAAI,aAAa,IAAI,MAAM;AAEvC,MACE,CAAC,OACA,YAAY,yBACX,YAAY,uBACZ,YAAY,sBAEd,QAAO,MAAM;AAGf,sBAAoB,IAAI;EAExB,MAAM,eAAe,IAAI,WAAW,OAAO,GACvC,MACA,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC,QAAQ,SAAS,OAAO;AAEzD,MAAI,4BAA4B,QAAQ,OAAO,eAAe;AAE9D,MAAI;AACF,OAAI,YAAY,uBAAuB;AACrC,QAAI,oCAAoC,eAAe;AAEvD,mBAAe,KAAK,KADD,MAAM,2BAA2B,QAAQ,WAAW,aAAa,CAChD;AACpC;;AAGF,OAAI,YAAY,qBAAqB;AACnC,QAAI,mBAAmB,eAAe;AACtC,QAAI;KACF,MAAM,MAAM,MAAM,YAAY,aAAa;AAC3C,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC,CAAC;aACzB,OAAO;AACd,SAAK,MAAgC,SAAS,UAAU;AACtD,UAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,UAAI,IAAI,iBAAiB;YACpB;AACL,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IAAI,KAAK,UAAU,EAAE,OAAQ,MAAgB,SAAS,CAAC,CAAC;;;AAGhE;;AAGF,OAAI,YAAY,uBAAuB;IACrC,MAAM,aAAa,IAAI,aAAa,IAAI,UAAU;IAClD,MAAM,eAAe,IAAI,aAAa,IAAI,YAAY;AAEtD,QAAI,CAAC,YAAY;AACf,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iCAAiC,CAAC,CAAC;AACnE;;IAGF,MAAM,UAAU,SAAS,YAAY,GAAG;AAExC,QAAI,YAAY,IAAI;AAClB,SAAI,2BAA2B,eAAe;AAE9C,oBAAe,KAAK,KADD,MAAM,mBAAmB,QAAQ,WAAW,aAAa,CACxC;AACpC;;AAGF,QAAI,iBAAiB,QAAQ,WAAW,gBAAgB,MAAM,OAAO,eAAe;IACpF,MAAM,WAAW,cAAc,IAAI,WAAW,UAAU,eAAe,cAAc,iBAAiB;AAEtG,mBAAe,KAAK,KADD,MAAM,cAAc,QAAQ,WAAW,cAAc,SAAS,CAC7C;AACpC;;WAEK,OAAO;AACd,OAAI,sCAAsC,QAAQ;AAClD,OAAK,MAAgC,SAAS,UAAU;AACtD,QAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,QAAI,IAAI,iBAAiB;UACpB;AACL,QAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,QAAI,IAAI,KAAK,UAAU,EAAE,OAAQ,MAAgB,SAAS,CAAC,CAAC;;;;;AAMpE,eAAsB,iBACpB,KACA,KACA,WACe;CACf,MAAM,MAAM,MAAM,gBAAgB;AAClC,KAAI,IAAI,WAAW,UAAU;AAC3B,MAAI,UAAU,KAAK,EAAE,OAAO,UAAU,CAAC;AACvC,MAAI,KAAK;AACT;;AAEF,KAAI,sBAAsB,YAAY;CACtC,MAAM,YAAY,KAAK,WAAW,SAAS;CAC3C,MAAM,aAAa;AACnB,MAAK,IAAI,UAAU,GAAG,UAAU,YAAY,UAC1C,KAAI;AACF,QAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AACrD;UACO,OAAY;AACnB,MAAI,MAAM,SAAS,SACjB;AAEF,MAAI,MAAM,SAAS,eAAe,UAAU,aAAa,GAAG;AAC1D,SAAM,IAAI,SAAS,YAAY,WAAW,SAAS,OAAO,UAAU,GAAG,CAAC;AACxE;;AAEF,MAAI,gCAAgC,UAAU,EAAE,WAAW,MAAM,UAAU;AAC3E,MAAI,YAAY,aAAa,EAC3B,KAAI,4BAA4B,WAAW,8BAA8B;;AAI/E,KAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,KAAI,IAAI,gBAAgB"}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AssetFunctions } from "./jitTranscodeMiddleware.js";
|
|
2
|
+
import { EfHandlers } from "./efHandlers.js";
|
|
3
|
+
import { ServerResponse } from "node:http";
|
|
4
|
+
import { IncomingMessage as IncomingMessage$1, NextFunction } from "connect";
|
|
5
|
+
|
|
6
|
+
//#region src/router.d.ts
|
|
7
|
+
interface DevServerOptions {
|
|
8
|
+
root: string;
|
|
9
|
+
cacheRoot: string;
|
|
10
|
+
handleRemoteUrls?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface RouterAssetFunctions extends AssetFunctions {
|
|
13
|
+
cacheImage: (cacheRoot: string, src: string) => Promise<any>;
|
|
14
|
+
findOrCreateCaptions: (cacheRoot: string, src: string) => Promise<any>;
|
|
15
|
+
md5FilePath: (src: string) => Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
type ConnectMiddleware = (req: IncomingMessage$1, res: ServerResponse, next: NextFunction) => void | Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Assemble the full Editframe middleware stack.
|
|
20
|
+
* Returns a single Connect-compatible middleware function that can be mounted
|
|
21
|
+
* onto any Connect/Express/Vite/http server.
|
|
22
|
+
*/
|
|
23
|
+
declare function createEditframeRouter(options: DevServerOptions, assetFns: RouterAssetFunctions, efHandlers: EfHandlers): ConnectMiddleware;
|
|
24
|
+
/**
|
|
25
|
+
* Inject the apiHost script into an HTML string.
|
|
26
|
+
* Each framework adapter calls this from its own HTML transform hook.
|
|
27
|
+
*/
|
|
28
|
+
declare function injectApiHostScript(html: string, origin: string): string;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { DevServerOptions, RouterAssetFunctions, createEditframeRouter, injectApiHostScript };
|
|
31
|
+
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { forbidRelativePaths } from "./forbidRelativePaths.js";
|
|
2
|
+
import { createJitTranscodeMiddleware } from "./jitTranscodeMiddleware.js";
|
|
3
|
+
import { createAssetsApiMiddleware, createLocalFilesApiMiddleware } from "./middleware.js";
|
|
4
|
+
import debug from "debug";
|
|
5
|
+
//#region src/router.ts
|
|
6
|
+
/**
|
|
7
|
+
* Assemble the full Editframe middleware stack.
|
|
8
|
+
* Returns a single Connect-compatible middleware function that can be mounted
|
|
9
|
+
* onto any Connect/Express/Vite/http server.
|
|
10
|
+
*/
|
|
11
|
+
function createEditframeRouter(options, assetFns, efHandlers) {
|
|
12
|
+
const jitMiddleware = createJitTranscodeMiddleware({
|
|
13
|
+
root: options.root,
|
|
14
|
+
cacheRoot: options.cacheRoot,
|
|
15
|
+
handleRemoteUrls: options.handleRemoteUrls
|
|
16
|
+
}, assetFns);
|
|
17
|
+
const assetsMiddleware = createAssetsApiMiddleware(options, assetFns);
|
|
18
|
+
const filesMiddleware = createLocalFilesApiMiddleware(options, assetFns);
|
|
19
|
+
const efMiddleware = async (req, res, next) => {
|
|
20
|
+
const log = debug("ef:dev-server");
|
|
21
|
+
if (!req.url?.startsWith("/@ef")) return next();
|
|
22
|
+
forbidRelativePaths(req);
|
|
23
|
+
log(`Handling ${req.url} at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
24
|
+
const cacheRoot = options.cacheRoot.replace("dist/", "src/");
|
|
25
|
+
const efPrefix = req.url.split("/")[1];
|
|
26
|
+
switch (efPrefix) {
|
|
27
|
+
case "@ef-clear-cache":
|
|
28
|
+
await efHandlers.clearCache(req, res, cacheRoot);
|
|
29
|
+
break;
|
|
30
|
+
case "@ef-sign-url":
|
|
31
|
+
await efHandlers.signUrl(req, res);
|
|
32
|
+
break;
|
|
33
|
+
case "@ef-write-snapshot":
|
|
34
|
+
if (efHandlers.writeSnapshot) await efHandlers.writeSnapshot(req, res);
|
|
35
|
+
else {
|
|
36
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
37
|
+
res.end(JSON.stringify({ error: "Snapshot endpoints not available in this configuration" }));
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case "@ef-compare-snapshot":
|
|
41
|
+
if (efHandlers.compareSnapshot) await efHandlers.compareSnapshot(req, res);
|
|
42
|
+
else {
|
|
43
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
44
|
+
res.end(JSON.stringify({ error: "Snapshot endpoints not available in this configuration" }));
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case "@ef-compare-two-images":
|
|
48
|
+
if (efHandlers.compareTwoImages) await efHandlers.compareTwoImages(req, res);
|
|
49
|
+
else {
|
|
50
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
51
|
+
res.end(JSON.stringify({ error: "Snapshot endpoints not available in this configuration" }));
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
log(`Unknown ef prefix: ${efPrefix}`);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return (req, res, next) => {
|
|
60
|
+
jitMiddleware(req, res, () => {
|
|
61
|
+
assetsMiddleware(req, res, () => {
|
|
62
|
+
filesMiddleware(req, res, () => {
|
|
63
|
+
efMiddleware(req, res, next);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Inject the apiHost script into an HTML string.
|
|
71
|
+
* Each framework adapter calls this from its own HTML transform hook.
|
|
72
|
+
*/
|
|
73
|
+
function injectApiHostScript(html, origin) {
|
|
74
|
+
const script = `<script>window.__EDITFRAME__ = Object.assign({}, window.__EDITFRAME__, { apiHost: "${origin}" });<\/script>`;
|
|
75
|
+
return html.replace(/(<head[^>]*>)/i, `$1${script}`);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { createEditframeRouter, injectApiHostScript };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","names":[],"sources":["../src/router.ts"],"mappings":";;;;;;;;;;AAoCA,SAAgB,sBACd,SACA,UACA,YACmB;CAOnB,MAAM,gBAAgB,6BANmB;EACvC,MAAM,QAAQ;EACd,WAAW,QAAQ;EACnB,kBAAkB,QAAQ;EAC3B,EAE8D,SAAS;CACxE,MAAM,mBAAmB,0BAA0B,SAAS,SAAS;CACrE,MAAM,kBAAkB,8BAA8B,SAAS,SAAS;CAExE,MAAM,eAAe,OAAO,KAAsB,KAAqB,SAAuB;EAC5F,MAAM,MAAM,MAAM,gBAAgB;AAElC,MAAI,CAAC,IAAI,KAAK,WAAW,OAAO,CAC9B,QAAO,MAAM;AAGf,sBAAoB,IAAI;AACxB,MAAI,YAAY,IAAI,IAAI,uBAAM,IAAI,MAAM,EAAC,aAAa,GAAG;EAEzD,MAAM,YAAY,QAAQ,UAAU,QAAQ,SAAS,OAAO;EAC5D,MAAM,WAAW,IAAI,IAAI,MAAM,IAAI,CAAC;AAEpC,UAAQ,UAAR;GACE,KAAK;AACH,UAAM,WAAW,WAAW,KAAK,KAAK,UAAU;AAChD;GACF,KAAK;AACH,UAAM,WAAW,QAAQ,KAAK,IAAI;AAClC;GACF,KAAK;AACH,QAAI,WAAW,cACb,OAAM,WAAW,cAAc,KAAK,IAAI;SACnC;AACL,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,0DAA0D,CAAC,CAAC;;AAE9F;GACF,KAAK;AACH,QAAI,WAAW,gBACb,OAAM,WAAW,gBAAgB,KAAK,IAAI;SACrC;AACL,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,0DAA0D,CAAC,CAAC;;AAE9F;GACF,KAAK;AACH,QAAI,WAAW,iBACb,OAAM,WAAW,iBAAiB,KAAK,IAAI;SACtC;AACL,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,0DAA0D,CAAC,CAAC;;AAE9F;GACF;AACE,QAAI,sBAAsB,WAAW;AACrC;;;AAKN,SAAQ,KAAsB,KAAqB,SAAuB;AACxE,gBAAc,KAAK,WAAW;AAC5B,oBAAiB,KAAK,WAAW;AAC/B,oBAAgB,KAAK,WAAW;AAC9B,kBAAa,KAAK,KAAK,KAAK;MAC5B;KACF;IACF;;;;;;;AAQN,SAAgB,oBAAoB,MAAc,QAAwB;CACxE,MAAM,SAAS,sFAAsF,OAAO;AAC5G,QAAO,KAAK,QAAQ,kBAAkB,KAAK,SAAS"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { TaskResult } from "@editframe/assets";
|
|
3
|
+
|
|
4
|
+
//#region src/sendTaskResult.d.ts
|
|
5
|
+
declare const sendTaskResult: (req: IncomingMessage, res: ServerResponse<IncomingMessage>, taskResult: TaskResult) => ServerResponse<IncomingMessage> | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { sendTaskResult };
|
|
8
|
+
//# sourceMappingURL=sendTaskResult.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
import { createReadStream, statSync } from "node:fs";
|
|
3
|
+
import mime from "mime";
|
|
4
|
+
//#region src/sendTaskResult.ts
|
|
5
|
+
const sendTaskResult = (req, res, taskResult) => {
|
|
6
|
+
const { cachePath, md5Sum } = taskResult;
|
|
7
|
+
const filePath = cachePath;
|
|
8
|
+
const headers = { etag: md5Sum };
|
|
9
|
+
const log = debug("ef:sendfile");
|
|
10
|
+
const sendStartTime = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
const stats = statSync(filePath);
|
|
13
|
+
log(`Sending file ${filePath} (size: ${stats.size} bytes)`);
|
|
14
|
+
if (req.headers.range) {
|
|
15
|
+
const [x, y] = req.headers.range.replace("bytes=", "").split("-");
|
|
16
|
+
let end = Number.parseInt(y ?? "0", 10) || stats.size - 1;
|
|
17
|
+
const start = Number.parseInt(x ?? "0", 10) || 0;
|
|
18
|
+
if (end >= stats.size) end = stats.size - 1;
|
|
19
|
+
if (start >= stats.size) {
|
|
20
|
+
log("Range start is greater than file size");
|
|
21
|
+
res.setHeader("Content-Range", `bytes */${stats.size}`);
|
|
22
|
+
res.statusCode = 416;
|
|
23
|
+
return res.end();
|
|
24
|
+
}
|
|
25
|
+
res.writeHead(206, {
|
|
26
|
+
...headers,
|
|
27
|
+
"Content-Type": mime.getType(filePath) || "text/plain",
|
|
28
|
+
"Cache-Control": "max-age=3600",
|
|
29
|
+
"Content-Range": `bytes ${start}-${end}/${stats.size}`,
|
|
30
|
+
"Content-Length": end - start + 1,
|
|
31
|
+
"Accept-Ranges": "bytes"
|
|
32
|
+
});
|
|
33
|
+
log(`Sending ${filePath} range ${start}-${end}/${stats.size}`);
|
|
34
|
+
const readStream = createReadStream(filePath, {
|
|
35
|
+
start,
|
|
36
|
+
end
|
|
37
|
+
});
|
|
38
|
+
readStream.on("end", () => {
|
|
39
|
+
log(`Range request completed in ${Date.now() - sendStartTime}ms`);
|
|
40
|
+
});
|
|
41
|
+
readStream.pipe(res);
|
|
42
|
+
} else {
|
|
43
|
+
res.writeHead(200, {
|
|
44
|
+
...headers,
|
|
45
|
+
"Content-Type": mime.getType(filePath) || "text/plain",
|
|
46
|
+
"Cache-Control": "max-age=3600",
|
|
47
|
+
"Content-Length": stats.size
|
|
48
|
+
});
|
|
49
|
+
log(`Sending full file ${filePath} (${stats.size} bytes)`);
|
|
50
|
+
const readStream = createReadStream(filePath);
|
|
51
|
+
readStream.on("end", () => {
|
|
52
|
+
log(`File send completed in ${Date.now() - sendStartTime}ms`);
|
|
53
|
+
});
|
|
54
|
+
readStream.pipe(res);
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
log(`Error sending file after ${Date.now() - sendStartTime}ms:`, error);
|
|
58
|
+
if (!res.headersSent) {
|
|
59
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
60
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { sendTaskResult };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=sendTaskResult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendTaskResult.js","names":[],"sources":["../src/sendTaskResult.ts"],"mappings":";;;;AAMA,MAAa,kBACX,KACA,KACA,eACG;CACH,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,WAAW;CACjB,MAAM,UAAU,EACd,MAAM,QACP;CACD,MAAM,MAAM,MAAM,cAAc;CAChC,MAAM,gBAAgB,KAAK,KAAK;AAChC,KAAI;EACF,MAAM,QAAQ,SAAS,SAAS;AAChC,MAAI,gBAAgB,SAAS,UAAU,MAAM,KAAK,SAAS;AAE3D,MAAI,IAAI,QAAQ,OAAO;GACrB,MAAM,CAAC,GAAG,KAAK,IAAI,QAAQ,MAAM,QAAQ,UAAU,GAAG,CAAC,MAAM,IAAI;GACjE,IAAI,MAAM,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI,MAAM,OAAO;GACxD,MAAM,QAAQ,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI;AAE/C,OAAI,OAAO,MAAM,KACf,OAAM,MAAM,OAAO;AAGrB,OAAI,SAAS,MAAM,MAAM;AACvB,QAAI,wCAAwC;AAC5C,QAAI,UAAU,iBAAiB,WAAW,MAAM,OAAO;AACvD,QAAI,aAAa;AACjB,WAAO,IAAI,KAAK;;AAGlB,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,iBAAiB,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM;IAChD,kBAAkB,MAAM,QAAQ;IAChC,iBAAiB;IAClB,CAAC;AACF,OAAI,WAAW,SAAS,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM,OAAO;GAC9D,MAAM,aAAa,iBAAiB,UAAU;IAAE;IAAO;IAAK,CAAC;AAC7D,cAAW,GAAG,aAAa;AAEzB,QAAI,8BADY,KAAK,KAAK,GAAG,cACa,IAAI;KAC9C;AACF,cAAW,KAAK,IAAI;SACf;AACL,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,kBAAkB,MAAM;IACzB,CAAC;AACF,OAAI,qBAAqB,SAAS,IAAI,MAAM,KAAK,SAAS;GAC1D,MAAM,aAAa,iBAAiB,SAAS;AAC7C,cAAW,GAAG,aAAa;AAEzB,QAAI,0BADY,KAAK,KAAK,GAAG,cACS,IAAI;KAC1C;AACF,cAAW,KAAK,IAAI;;UAEf,OAAO;AAEd,MAAI,4BADY,KAAK,KAAK,GAAG,cACW,MAAM,MAAM;AACpD,MAAI,CAAC,IAAI,aAAa;AACpB,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAQ,MAAgB,SAAS,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EfHandlers } from "./efHandlers.js";
|
|
2
|
+
import { DevServerOptions, RouterAssetFunctions } from "./router.js";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
|
|
5
|
+
//#region src/server.d.ts
|
|
6
|
+
interface EditframeDevServerOptions extends DevServerOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a standalone http.Server that serves the full Editframe dev media API.
|
|
11
|
+
*
|
|
12
|
+
* Use this for toolchains that do not expose a Connect middleware stack
|
|
13
|
+
* (e.g. Webpack, Rspack, Next.js custom server). The server listens on its
|
|
14
|
+
* own port; adapters inject window.__EDITFRAME__.apiHost pointing at that port.
|
|
15
|
+
*
|
|
16
|
+
* Vite adapters should prefer createEditframeRouter() and mount it directly
|
|
17
|
+
* onto server.middlewares instead.
|
|
18
|
+
*/
|
|
19
|
+
declare function createEditframeDevServer(options: EditframeDevServerOptions, assetFns: RouterAssetFunctions, efHandlers: EfHandlers): http.Server;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { EditframeDevServerOptions, createEditframeDevServer };
|
|
22
|
+
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createEditframeRouter } from "./router.js";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
//#region src/server.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a standalone http.Server that serves the full Editframe dev media API.
|
|
6
|
+
*
|
|
7
|
+
* Use this for toolchains that do not expose a Connect middleware stack
|
|
8
|
+
* (e.g. Webpack, Rspack, Next.js custom server). The server listens on its
|
|
9
|
+
* own port; adapters inject window.__EDITFRAME__.apiHost pointing at that port.
|
|
10
|
+
*
|
|
11
|
+
* Vite adapters should prefer createEditframeRouter() and mount it directly
|
|
12
|
+
* onto server.middlewares instead.
|
|
13
|
+
*/
|
|
14
|
+
function createEditframeDevServer(options, assetFns, efHandlers) {
|
|
15
|
+
const router = createEditframeRouter(options, assetFns, efHandlers);
|
|
16
|
+
return http.createServer((req, res) => {
|
|
17
|
+
router(req, res, () => {
|
|
18
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
19
|
+
res.end("Not found");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createEditframeDevServer };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;;;AAoBA,SAAgB,yBACd,SACA,UACA,YACa;CACb,MAAM,SAAS,sBAAsB,SAAS,UAAU,WAAW;AAUnE,QARe,KAAK,cAAc,KAAsB,QAAwB;AAE9E,SAAO,KAAK,WAAW;AACrB,OAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,OAAI,IAAI,YAAY;IACpB;GACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@editframe/dev-server",
|
|
3
|
+
"version": "0.51.0",
|
|
4
|
+
"description": "Framework-agnostic Editframe dev media server",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"build": "tsdown",
|
|
17
|
+
"build:watch": "tsdown --watch"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "SEE LICENSE IN LICENSE-FULL.md",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@editframe/api": "0.51.0",
|
|
24
|
+
"@editframe/assets": "0.51.0",
|
|
25
|
+
"connect": "^3.7.0",
|
|
26
|
+
"debug": "^4.3.5",
|
|
27
|
+
"mime": "^4.0.3",
|
|
28
|
+
"odiff-bin": "^4.3.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/connect": "^3.4.38",
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
|
+
},
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|