Skip to content

Commit 6271fed

Browse files
Merge pull request OpenSignLabs#2183 from OpenSignLabs/updates-26432416990
Merge pull request #2474 from nxglabs/sync-to-public_repo-26403170706
2 parents 0c29596 + d3cb8d5 commit 6271fed

3 files changed

Lines changed: 186 additions & 11 deletions

File tree

apps/OpenSign/server.cjs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env node
2+
/* eslint-env node */
3+
/* eslint-disable no-console */
4+
// Tiny static file server for the production build.
5+
// - Serves real files from ./build (including dotfile dirs like .well-known)
6+
// - Falls back to /index.html only when the requested path does not exist
7+
// (SPA client-side routing).
8+
const http = require("node:http");
9+
const fs = require("node:fs");
10+
const path = require("node:path");
11+
12+
const root = path.join(__dirname, "build");
13+
const port = Number(process.env.PORT) || 3000;
14+
const host = process.env.HOST || "0.0.0.0";
15+
16+
const mime = {
17+
".html": "text/html; charset=utf-8",
18+
".js": "application/javascript; charset=utf-8",
19+
".mjs": "application/javascript; charset=utf-8",
20+
".css": "text/css; charset=utf-8",
21+
".json": "application/json; charset=utf-8",
22+
".map": "application/json; charset=utf-8",
23+
".svg": "image/svg+xml",
24+
".png": "image/png",
25+
".jpg": "image/jpeg",
26+
".jpeg": "image/jpeg",
27+
".gif": "image/gif",
28+
".ico": "image/x-icon",
29+
".webp": "image/webp",
30+
".woff": "font/woff",
31+
".woff2": "font/woff2",
32+
".ttf": "font/ttf",
33+
".otf": "font/otf",
34+
".txt": "text/plain; charset=utf-8",
35+
".pdf": "application/pdf",
36+
".csv": "text/csv; charset=utf-8",
37+
".wasm": "application/wasm",
38+
".xml": "application/xml; charset=utf-8"
39+
};
40+
41+
function contentType(filePath) {
42+
return mime[path.extname(filePath).toLowerCase()] || "application/octet-stream";
43+
}
44+
45+
46+
function safeJoin(reqPath) {
47+
let decoded;
48+
try {
49+
decoded = decodeURIComponent(reqPath.split("?")[0].split("#")[0]);
50+
} catch {
51+
return null;
52+
}
53+
const resolved = path.normalize(path.join(root, decoded));
54+
if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
55+
return resolved;
56+
}
57+
58+
function cacheControl(filePath) {
59+
const ext = path.extname(filePath).toLowerCase();
60+
const relativePath = path.relative(root, filePath);
61+
const isBuildAsset =
62+
relativePath === "assets" ||
63+
relativePath.startsWith(`assets${path.sep}`);
64+
65+
if (ext === ".html" || ext === "") {
66+
return "no-cache";
67+
}
68+
69+
return isBuildAsset
70+
? "public, max-age=31536000, immutable"
71+
: "no-cache";
72+
}
73+
74+
function streamFile(req, res, filePath, stats) {
75+
const headers = {
76+
"Content-Type": contentType(filePath),
77+
"Content-Length": stats.size,
78+
"Last-Modified": stats.mtime.toUTCString(),
79+
"Cache-Control": cacheControl(filePath)
80+
};
81+
if (req.method === "HEAD") {
82+
res.writeHead(200, headers);
83+
return res.end();
84+
}
85+
const stream = fs.createReadStream(filePath);
86+
stream.on("error", (err) => {
87+
console.error("Stream error:", err);
88+
if (!res.headersSent) {
89+
res.writeHead(500, { "Content-Type": "text/plain" });
90+
res.end("Internal Server Error");
91+
} else {
92+
res.destroy();
93+
}
94+
});
95+
res.on("close", () => {
96+
if (!stream.destroyed) stream.destroy();
97+
});
98+
res.on("error", (err) => {
99+
console.error("Response error:", err);
100+
if (!stream.destroyed) stream.destroy();
101+
});
102+
res.writeHead(200, headers);
103+
stream.pipe(res);
104+
}
105+
106+
function sendIndex(req, res) {
107+
const indexPath = path.join(root, "index.html");
108+
fs.stat(indexPath, (err, stats) => {
109+
if (err || !stats || !stats.isFile()) {
110+
res.writeHead(500, { "Content-Type": "text/plain" });
111+
return res.end("index.html not found");
112+
}
113+
streamFile(req, res, indexPath, stats);
114+
});
115+
}
116+
117+
const server = http.createServer((req, res) => {
118+
if (req.method === "OPTIONS") {
119+
const filePath = safeJoin(req.url || "/");
120+
res.writeHead(204);
121+
return res.end();
122+
}
123+
124+
if (req.method !== "GET" && req.method !== "HEAD") {
125+
res.writeHead(405, { Allow: "GET, HEAD, OPTIONS" });
126+
return res.end();
127+
}
128+
129+
const reqUrl = req.url || "/";
130+
const filePath = safeJoin(reqUrl);
131+
if (!filePath) {
132+
res.writeHead(400);
133+
return res.end("Bad Request");
134+
}
135+
136+
fs.stat(filePath, (err, stats) => {
137+
if (err) {
138+
// No file at this path → SPA fallback to index.html
139+
return sendIndex(req, res);
140+
}
141+
if (stats.isFile()) {
142+
return streamFile(req, res, filePath, stats);
143+
}
144+
if (stats.isDirectory()) {
145+
const indexInDir = path.join(filePath, "index.html");
146+
return fs.stat(indexInDir, (dirErr, dirStats) => {
147+
if (!dirErr && dirStats && dirStats.isFile()) {
148+
return streamFile(req, res, indexInDir, dirStats);
149+
}
150+
return sendIndex(req, res);
151+
});
152+
}
153+
// Path exists but is neither file nor directory → SPA fallback
154+
return sendIndex(req, res);
155+
});
156+
});
157+
158+
server.listen(port, host, () => {
159+
console.log(`Serving ${root} on http://${host}:${port}`);
160+
});

apps/OpenSign/src/components/bulksend/components/PrefillWidgets.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ const PrefillWidgets = ({ prefills = [], setPrefills, onNext }) => {
3232
>
3333
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400">
3434
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-4 w-full">
35-
{prefills.map((widget, index) => (
35+
{[...prefills]
36+
.sort((a, b) =>
37+
a.pageNumber !== b.pageNumber
38+
? a.pageNumber - b.pageNumber
39+
: (a.yPosition ?? 0) - (b.yPosition ?? 0)
40+
).map((widget, index) => (
3641
<RenderWidgets
3742
key={widget.key}
3843
showLabel

apps/OpenSign/src/components/pdf/PrefillWidgetsModal.jsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,25 @@ function PrefillWidgetModal(props) {
148148
return true;
149149
})
150150
}));
151-
//latten the filtered array and exclude read-only widgets
152-
const flatArray = filteredArray?.flatMap((page) =>
153-
page.pos
154-
.filter((widget) => !widget.options?.isReadOnly)
155-
.map((widget) => ({
156-
widget,
157-
pageNumber: page.pageNumber
158-
}))
159-
);
151+
// Flatten the filtered array, exclude read-only widgets,
152+
// carry yPosition for sorting, then sort by pageNumber asc → yPosition asc
153+
// (mirrors the newSignPos.sort in PdfRequestFiles so widgets appear in
154+
// the same top-to-bottom, page-1-first order as they do in the document)
155+
const flatArray = filteredArray
156+
?.flatMap((page) =>
157+
page.pos
158+
.filter((widget) => !widget.options?.isReadOnly)
159+
.map((widget) => ({
160+
widget,
161+
pageNumber: page.pageNumber,
162+
yPosition: widget.yPosition ?? 0
163+
}))
164+
)
165+
?.sort((a, b) =>
166+
a.pageNumber !== b.pageNumber
167+
? a.pageNumber - b.pageNumber // primary: page order (page 1 first)
168+
: a.yPosition - b.yPosition // secondary: top-to-bottom within page
169+
);
160170

161171
return flatArray || [];
162172
}, [props.prefillData]);
@@ -896,4 +906,4 @@ function PrefillWidgetModal(props) {
896906
);
897907
}
898908

899-
export default PrefillWidgetModal;
909+
export default PrefillWidgetModal;

0 commit comments

Comments
 (0)