641 lines
18 KiB
JavaScript
641 lines
18 KiB
JavaScript
// lib/lib-utils.ts
|
|
import { createHmac, createPrivateKey, createPublicKey, timingSafeEqual, verify, sign } from "node:crypto";
|
|
function generateHmacSha256(data, secretKey) {
|
|
const payload = typeof data === "string" ? data : JSON.stringify(sortObjectKeys(data));
|
|
return createHmac("sha256", secretKey).update(payload, "utf8").digest("hex");
|
|
}
|
|
function signEd25519(message, privateKeyHex) {
|
|
const rawKeyBuffer = Buffer.from(privateKeyHex, "hex");
|
|
const seed = rawKeyBuffer.length === 64 ? rawKeyBuffer.subarray(0, 32) : rawKeyBuffer;
|
|
const pkcs8Header = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
const pkcs8Buffer = Buffer.concat([pkcs8Header, seed]);
|
|
const privateKey = createPrivateKey({
|
|
key: pkcs8Buffer,
|
|
format: "der",
|
|
type: "pkcs8"
|
|
});
|
|
const dataBuffer = typeof message === "string" ? Buffer.from(message, "utf-8") : message;
|
|
const signatureBuffer = sign(null, dataBuffer, privateKey);
|
|
return signatureBuffer.toString("hex");
|
|
}
|
|
function sortObjectKeys(obj) {
|
|
return Object.keys(obj).sort().reduce((result, key) => {
|
|
result[key] = obj[key];
|
|
return result;
|
|
}, {});
|
|
}
|
|
|
|
// lib/lib-tmpl.ts
|
|
import PizzaZip from "pizzip";
|
|
import {
|
|
DOMParser,
|
|
XMLSerializer
|
|
} from "@xmldom/xmldom";
|
|
import xpath from "xpath";
|
|
var getCreateReport = async () => {
|
|
const mod = await import("docx-templates");
|
|
const createReportFn = typeof mod.createReport === "function" ? mod.createReport : typeof mod.default === "function" ? mod.default : void 0;
|
|
if (!createReportFn) {
|
|
throw new Error("docx-templates createReport is not available");
|
|
}
|
|
return createReportFn;
|
|
};
|
|
var WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
var asXpathNode = (node) => node;
|
|
function createSelector() {
|
|
return xpath.useNamespaces({ w: WORD_NS });
|
|
}
|
|
function detectSdtControlType(sdt, select) {
|
|
const sdtPr = select(
|
|
"./w:sdtPr",
|
|
asXpathNode(sdt),
|
|
true
|
|
);
|
|
if (!sdtPr) {
|
|
return { type: "other", multiLine: false };
|
|
}
|
|
const textEl = select(
|
|
"./w:text",
|
|
asXpathNode(sdtPr),
|
|
true
|
|
);
|
|
if (textEl) {
|
|
const multiLineAttr = select(
|
|
"./@w:multiLine",
|
|
asXpathNode(textEl),
|
|
true
|
|
);
|
|
return {
|
|
type: "plain",
|
|
multiLine: multiLineAttr?.value === "1" || multiLineAttr?.value === "true"
|
|
};
|
|
}
|
|
const richTextEl = select(
|
|
"./w:richText",
|
|
asXpathNode(sdtPr),
|
|
true
|
|
);
|
|
if (richTextEl) {
|
|
return { type: "rich", multiLine: true };
|
|
}
|
|
const otherTypeTags = [
|
|
"w:date",
|
|
"w:dropDownList",
|
|
"w:comboBox",
|
|
"w:checkbox",
|
|
"w:citation",
|
|
"w:group",
|
|
"w:docPartObj",
|
|
"w:docPartList"
|
|
];
|
|
for (const tag of otherTypeTags) {
|
|
const found = select(
|
|
`./${tag}`,
|
|
asXpathNode(sdtPr),
|
|
true
|
|
);
|
|
if (found) {
|
|
return { type: "other", multiLine: false };
|
|
}
|
|
}
|
|
return { type: "rich", multiLine: true };
|
|
}
|
|
function detectSdtStructure(sdtContent, select) {
|
|
const directP = select(
|
|
"./w:p",
|
|
asXpathNode(sdtContent)
|
|
);
|
|
return directP.length > 0 ? "block" : "inline";
|
|
}
|
|
function buildRunChildren(doc, text) {
|
|
const nodes = [];
|
|
const tokens = text.split(/(\t|\n)/);
|
|
tokens.forEach((token) => {
|
|
if (token === "") return;
|
|
if (token === " ") {
|
|
nodes.push(doc.createElementNS(WORD_NS, "w:tab"));
|
|
} else if (token === "\n") {
|
|
nodes.push(doc.createElementNS(WORD_NS, "w:br"));
|
|
} else {
|
|
const t = doc.createElementNS(WORD_NS, "w:t");
|
|
t.setAttribute("xml:space", "preserve");
|
|
t.textContent = token;
|
|
nodes.push(t);
|
|
}
|
|
});
|
|
if (nodes.length === 0) {
|
|
const t = doc.createElementNS(WORD_NS, "w:t");
|
|
t.setAttribute("xml:space", "preserve");
|
|
t.textContent = "";
|
|
nodes.push(t);
|
|
}
|
|
return nodes;
|
|
}
|
|
function buildRun(doc, text, rPr) {
|
|
const run = doc.createElementNS(WORD_NS, "w:r");
|
|
if (rPr) {
|
|
run.appendChild(rPr.cloneNode(true));
|
|
}
|
|
buildRunChildren(doc, text).forEach(
|
|
(node) => run.appendChild(node)
|
|
);
|
|
return run;
|
|
}
|
|
function insertInlineContent(doc, sdtContent, select, text, allowLineBreak) {
|
|
const safeText = allowLineBreak ? text : text.replace(/\n/g, " ");
|
|
const existingRuns = select(
|
|
"./w:r",
|
|
asXpathNode(sdtContent)
|
|
);
|
|
const firstRPr = existingRuns.length > 0 ? select(
|
|
"./w:rPr",
|
|
asXpathNode(existingRuns[0]),
|
|
true
|
|
) : void 0;
|
|
existingRuns.forEach((r) => {
|
|
if (r.parentNode) r.parentNode.removeChild(r);
|
|
});
|
|
sdtContent.appendChild(
|
|
buildRun(doc, safeText, firstRPr)
|
|
);
|
|
}
|
|
function insertBlockContent(doc, sdtContent, select, text, allowMultiParagraph) {
|
|
const allParagraphs = select(
|
|
"./w:p",
|
|
asXpathNode(sdtContent)
|
|
);
|
|
if (allParagraphs.length === 0) {
|
|
const p = doc.createElementNS(WORD_NS, "w:p");
|
|
sdtContent.appendChild(p);
|
|
allParagraphs.push(p);
|
|
}
|
|
const firstP = allParagraphs[0];
|
|
const firstPPr = select(
|
|
"./w:pPr",
|
|
asXpathNode(firstP),
|
|
true
|
|
);
|
|
const firstRun = select(
|
|
"./w:r",
|
|
asXpathNode(firstP),
|
|
true
|
|
);
|
|
const firstRPr = firstRun ? select(
|
|
"./w:rPr",
|
|
asXpathNode(firstRun),
|
|
true
|
|
) : void 0;
|
|
allParagraphs.forEach((p) => {
|
|
if (p !== firstP && p.parentNode) {
|
|
p.parentNode.removeChild(p);
|
|
}
|
|
});
|
|
const existingRuns = select(
|
|
"./w:r",
|
|
asXpathNode(firstP)
|
|
);
|
|
existingRuns.forEach((r) => {
|
|
if (r.parentNode) r.parentNode.removeChild(r);
|
|
});
|
|
if (!allowMultiParagraph) {
|
|
firstP.appendChild(buildRun(doc, text.replace(/\n+/g, "\n"), firstRPr));
|
|
return;
|
|
}
|
|
const paragraphs = text.split("\n\n");
|
|
paragraphs.forEach((pText, index) => {
|
|
let targetP;
|
|
if (index === 0) {
|
|
targetP = firstP;
|
|
} else {
|
|
targetP = doc.createElementNS(WORD_NS, "w:p");
|
|
if (firstPPr) {
|
|
targetP.appendChild(firstPPr.cloneNode(true));
|
|
}
|
|
sdtContent.appendChild(targetP);
|
|
}
|
|
const run = doc.createElementNS(WORD_NS, "w:r");
|
|
if (firstRPr) {
|
|
run.appendChild(firstRPr.cloneNode(true));
|
|
}
|
|
buildRunChildren(doc, pText).forEach(
|
|
(node) => run.appendChild(node)
|
|
);
|
|
targetP.appendChild(run);
|
|
});
|
|
}
|
|
function insertFallbackContent(sdtContent, select, text) {
|
|
const textNodes = select(
|
|
".//w:t",
|
|
asXpathNode(sdtContent)
|
|
);
|
|
if (textNodes.length === 0) return;
|
|
textNodes[0].textContent = text;
|
|
for (let i = 1; i < textNodes.length; i++) {
|
|
textNodes[i].textContent = "";
|
|
}
|
|
}
|
|
function normalizeValue(value) {
|
|
if (value === null || value === void 0) {
|
|
return "";
|
|
}
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
return String(value);
|
|
}
|
|
return JSON.stringify(value);
|
|
}
|
|
function templateContentControls(templateBuffer, data) {
|
|
const zip = new PizzaZip(templateBuffer);
|
|
const docXmlFile = zip.file("word/document.xml");
|
|
if (!docXmlFile) {
|
|
throw new Error("Invalid docx file: word/document.xml not found.");
|
|
}
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(docXmlFile.asText(), "text/xml");
|
|
const select = createSelector();
|
|
const sdtNodes = select(
|
|
"//w:sdt",
|
|
asXpathNode(doc)
|
|
);
|
|
sdtNodes.forEach((sdt) => {
|
|
const tagAttr = select(
|
|
"./w:sdtPr/w:tag/@w:val",
|
|
asXpathNode(sdt),
|
|
true
|
|
);
|
|
const tagName = tagAttr?.value;
|
|
if (!tagName || !Object.prototype.hasOwnProperty.call(data, tagName)) {
|
|
return;
|
|
}
|
|
const newValue = normalizeValue(data[tagName]);
|
|
const sdtContent = select(
|
|
"./w:sdtContent",
|
|
asXpathNode(sdt),
|
|
true
|
|
);
|
|
if (!sdtContent) return;
|
|
const { type: controlType, multiLine } = detectSdtControlType(sdt, select);
|
|
const structure = detectSdtStructure(sdtContent, select);
|
|
if (controlType === "other") {
|
|
insertFallbackContent(sdtContent, select, newValue);
|
|
return;
|
|
}
|
|
if (structure === "inline") {
|
|
const allowLineBreak = controlType === "plain" ? multiLine : true;
|
|
insertInlineContent(
|
|
doc,
|
|
sdtContent,
|
|
select,
|
|
newValue,
|
|
allowLineBreak
|
|
);
|
|
} else {
|
|
insertBlockContent(
|
|
doc,
|
|
sdtContent,
|
|
select,
|
|
newValue,
|
|
controlType === "rich"
|
|
);
|
|
}
|
|
});
|
|
const serializer = new XMLSerializer();
|
|
zip.file(
|
|
"word/document.xml",
|
|
serializer.serializeToString(doc)
|
|
);
|
|
return zip.generate({
|
|
type: "nodebuffer",
|
|
compression: "DEFLATE"
|
|
});
|
|
}
|
|
var CUSTOM_NS = "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
|
|
var VT_NS = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
|
|
var REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
var CONTENT_TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
var XMLNS_NS = "http://www.w3.org/2000/xmlns/";
|
|
var CUSTOM_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
|
|
var CUSTOM_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.custom-properties+xml";
|
|
var CUSTOM_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
|
|
var CUSTOM_PATH = "docProps/custom.xml";
|
|
function parseXml(xml, fileName) {
|
|
const errors = [];
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(
|
|
xml,
|
|
"application/xml"
|
|
);
|
|
if (errors.length > 0 || !doc.documentElement) {
|
|
throw new Error(
|
|
`Invalid ${fileName}: ${errors.join("; ")}`
|
|
);
|
|
}
|
|
return doc;
|
|
}
|
|
function assertRoot(doc, namespace, localName, fileName) {
|
|
const root = doc.documentElement;
|
|
if (root === null || root.localName !== localName || root.namespaceURI !== namespace) {
|
|
throw new Error(`Invalid root element in ${fileName}`);
|
|
}
|
|
return root;
|
|
}
|
|
function createCustomXml() {
|
|
const xml = [
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
`<Properties xmlns="${CUSTOM_NS}" xmlns:vt="${VT_NS}"/>`
|
|
].join("");
|
|
return parseXml(xml, CUSTOM_PATH);
|
|
}
|
|
function createValueElement(doc, value) {
|
|
let tagName;
|
|
let text;
|
|
if (value == null) {
|
|
tagName = "vt:lpwstr";
|
|
text = "";
|
|
} else if (typeof value === "string") {
|
|
tagName = "vt:lpwstr";
|
|
text = value;
|
|
} else if (typeof value === "boolean") {
|
|
tagName = "vt:bool";
|
|
text = value ? "true" : "false";
|
|
} else if (typeof value === "number") {
|
|
if (!Number.isFinite(value)) {
|
|
throw new TypeError(
|
|
`Invalid numeric property value: ${value}`
|
|
);
|
|
}
|
|
if (Number.isInteger(value)) {
|
|
if (!Number.isSafeInteger(value)) {
|
|
throw new TypeError(
|
|
`Integer property exceeds safe integer range: ${value}`
|
|
);
|
|
}
|
|
tagName = "vt:i8";
|
|
} else {
|
|
tagName = "vt:r8";
|
|
}
|
|
text = String(value);
|
|
} else if (value instanceof Date) {
|
|
if (Number.isNaN(value.getTime())) {
|
|
throw new TypeError("Invalid Date property value");
|
|
}
|
|
tagName = "vt:filetime";
|
|
text = value.toISOString();
|
|
} else {
|
|
throw new TypeError(
|
|
`Unsupported custom property type: ${Object.prototype.toString.call(value)}`
|
|
);
|
|
}
|
|
const element = doc.createElementNS(VT_NS, tagName);
|
|
element.appendChild(doc.createTextNode(text));
|
|
return element;
|
|
}
|
|
function ensureCustomRelationship(zip) {
|
|
const fileName = "_rels/.rels";
|
|
const file = zip.file(fileName);
|
|
if (!file) {
|
|
throw new Error(`DOCX is missing ${fileName}`);
|
|
}
|
|
const doc = parseXml(file.asText(), fileName);
|
|
const root = assertRoot(
|
|
doc,
|
|
REL_NS,
|
|
"Relationships",
|
|
fileName
|
|
);
|
|
const relationships = Array.from(
|
|
root.getElementsByTagNameNS(REL_NS, "Relationship")
|
|
);
|
|
const existing = relationships.find(
|
|
(item) => item.getAttribute("Type") === CUSTOM_REL_TYPE
|
|
);
|
|
if (existing) {
|
|
if (existing.getAttribute("Target") !== CUSTOM_PATH) {
|
|
throw new Error(
|
|
"Existing custom-properties relationship has an unexpected Target"
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
const ids = new Set(
|
|
relationships.map((item) => item.getAttribute("Id"))
|
|
);
|
|
let index = 1;
|
|
let relationshipId = `rId${index}`;
|
|
while (ids.has(relationshipId)) {
|
|
index++;
|
|
relationshipId = `rId${index}`;
|
|
}
|
|
const relationship = doc.createElementNS(
|
|
REL_NS,
|
|
"Relationship"
|
|
);
|
|
relationship.setAttribute("Id", relationshipId);
|
|
relationship.setAttribute("Type", CUSTOM_REL_TYPE);
|
|
relationship.setAttribute("Target", CUSTOM_PATH);
|
|
root.appendChild(relationship);
|
|
zip.file(
|
|
fileName,
|
|
new XMLSerializer().serializeToString(doc)
|
|
);
|
|
}
|
|
function ensureCustomContentType(zip) {
|
|
const fileName = "[Content_Types].xml";
|
|
const file = zip.file(fileName);
|
|
if (!file) {
|
|
throw new Error(`DOCX is missing ${fileName}`);
|
|
}
|
|
const doc = parseXml(file.asText(), fileName);
|
|
const root = assertRoot(
|
|
doc,
|
|
CONTENT_TYPES_NS,
|
|
"Types",
|
|
fileName
|
|
);
|
|
const overrides = Array.from(
|
|
root.getElementsByTagNameNS(
|
|
CONTENT_TYPES_NS,
|
|
"Override"
|
|
)
|
|
);
|
|
const existing = overrides.find(
|
|
(item) => item.getAttribute("PartName") === "/docProps/custom.xml"
|
|
);
|
|
if (existing) {
|
|
existing.setAttribute("ContentType", CUSTOM_CONTENT_TYPE);
|
|
} else {
|
|
const override = doc.createElementNS(
|
|
CONTENT_TYPES_NS,
|
|
"Override"
|
|
);
|
|
override.setAttribute(
|
|
"PartName",
|
|
"/docProps/custom.xml"
|
|
);
|
|
override.setAttribute(
|
|
"ContentType",
|
|
CUSTOM_CONTENT_TYPE
|
|
);
|
|
root.appendChild(override);
|
|
}
|
|
zip.file(
|
|
fileName,
|
|
new XMLSerializer().serializeToString(doc)
|
|
);
|
|
}
|
|
function setCustomDocProps(docxBuffer, data) {
|
|
const zip = new PizzaZip(docxBuffer);
|
|
const customFile = zip.file(CUSTOM_PATH);
|
|
const doc = customFile ? parseXml(customFile.asText(), CUSTOM_PATH) : createCustomXml();
|
|
const root = assertRoot(
|
|
doc,
|
|
CUSTOM_NS,
|
|
"Properties",
|
|
CUSTOM_PATH
|
|
);
|
|
root.setAttributeNS(XMLNS_NS, "xmlns:vt", VT_NS);
|
|
const properties = Array.from(
|
|
root.getElementsByTagNameNS(CUSTOM_NS, "property")
|
|
);
|
|
const propertyMap = /* @__PURE__ */ new Map();
|
|
const usedPids = /* @__PURE__ */ new Set();
|
|
for (const property of properties) {
|
|
const name = property.getAttribute("name");
|
|
const pidText = property.getAttribute("pid");
|
|
const pid = Number(pidText);
|
|
if (!name) {
|
|
throw new Error(
|
|
"Custom property is missing its name attribute"
|
|
);
|
|
}
|
|
if (propertyMap.has(name)) {
|
|
throw new Error(`Duplicate custom property name: ${name}`);
|
|
}
|
|
if (pidText === null || !/^[1-9]\d*$/.test(pidText) || !Number.isSafeInteger(pid) || pid < 2) {
|
|
throw new Error(
|
|
`Invalid custom property pid for "${name}": ${pidText}`
|
|
);
|
|
}
|
|
if (usedPids.has(pid)) {
|
|
throw new Error(`Duplicate custom property pid: ${pid}`);
|
|
}
|
|
propertyMap.set(name, property);
|
|
usedPids.add(pid);
|
|
}
|
|
let nextPid = 2;
|
|
for (const [name, value] of Object.entries(data)) {
|
|
if (!name.trim()) {
|
|
throw new Error("Custom property name must not be empty");
|
|
}
|
|
let property = propertyMap.get(name);
|
|
if (!property) {
|
|
while (usedPids.has(nextPid)) {
|
|
nextPid++;
|
|
}
|
|
property = doc.createElementNS(
|
|
CUSTOM_NS,
|
|
"property"
|
|
);
|
|
property.setAttribute("fmtid", CUSTOM_FMTID);
|
|
property.setAttribute("pid", String(nextPid));
|
|
property.setAttribute("name", name);
|
|
usedPids.add(nextPid);
|
|
nextPid++;
|
|
root.appendChild(property);
|
|
propertyMap.set(name, property);
|
|
}
|
|
const valueElement = createValueElement(doc, value);
|
|
while (property.firstChild) {
|
|
property.removeChild(property.firstChild);
|
|
}
|
|
property.appendChild(valueElement);
|
|
}
|
|
zip.file(
|
|
CUSTOM_PATH,
|
|
new XMLSerializer().serializeToString(doc)
|
|
);
|
|
ensureCustomRelationship(zip);
|
|
ensureCustomContentType(zip);
|
|
return Buffer.from(
|
|
zip.generate({
|
|
type: "nodebuffer",
|
|
compression: "DEFLATE"
|
|
})
|
|
);
|
|
}
|
|
function p_cc(title, tag, text = "", isMultiLine = true, lock = "sdtLocked") {
|
|
const lockTag = lock == "none" ? "" : `<w:lock w:val="${lock}" />`;
|
|
const multiLineTag = isMultiLine ? '<w:text w:multiLine="1" />' : "";
|
|
const content = escapeXml(text);
|
|
const xmlString = `<w:sdt><w:sdtPr><w:alias w:val="${title}" /><w:tag w:val="${tag}" /><w:id w:val="-1948841888" />${lockTag}<w:placeholder><w:docPart w:val="C3E615E9D8424D6ABEA7EADA60C3EFB6" /></w:placeholder>${multiLineTag}</w:sdtPr><w:sdtEndPr /><w:sdtContent><w:r><w:rPr><w:rFonts w:cs="TH SarabunPSK" /></w:rPr><w:t xml:space="preserve">${content}</w:t></w:r></w:sdtContent></w:sdt>`;
|
|
return `||${xmlString}||`;
|
|
}
|
|
function br(n) {
|
|
let text = "";
|
|
for (let i = 0; i < n; i++) text += "<w:br/>";
|
|
return `||${text}||`;
|
|
}
|
|
function bm(id, name) {
|
|
return `||<w:bookmarkStart w:id="${id}" w:name="${name}" /><w:bookmarkEnd w:id="${id}" />||`;
|
|
}
|
|
function escapeXml(unsafe) {
|
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
}
|
|
var templateDynamicContents = async (template, data) => {
|
|
const createReport = await getCreateReport();
|
|
return await createReport({
|
|
template,
|
|
data,
|
|
cmdDelimiter: ["{{", "}}"],
|
|
additionalJsContext: {
|
|
bm,
|
|
br,
|
|
p_cc
|
|
}
|
|
});
|
|
};
|
|
async function applyTemplate(templateBuffer, templateData, secret, private_key, ttl) {
|
|
const originalTicket_data = templateData?.info?.ticket_data;
|
|
if (originalTicket_data && typeof originalTicket_data === "string") {
|
|
try {
|
|
const originalTicketObj = JSON.parse(originalTicket_data);
|
|
if (originalTicketObj && typeof originalTicketObj === "object") {
|
|
console.log("Found valid ticket data: hash it");
|
|
const expireTimestamp = Math.floor(Date.now() / 1e3) + ttl;
|
|
const fullObj = { ...originalTicketObj, exp: expireTimestamp };
|
|
const sortedObj = sortObjectKeys(fullObj);
|
|
const ticket_data = JSON.stringify(sortedObj);
|
|
const HmacSha256 = generateHmacSha256(ticket_data, secret);
|
|
const Ed25519 = signEd25519(ticket_data, private_key);
|
|
templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 });
|
|
}
|
|
} catch (error) {
|
|
console.error("Set custom property fail", error);
|
|
}
|
|
}
|
|
if (templateData?.dc && typeof templateData?.dc === "object") {
|
|
templateBuffer = Buffer.from(await templateDynamicContents(templateBuffer, templateData.dc));
|
|
}
|
|
if (templateData?.cc && typeof templateData?.cc === "object") {
|
|
templateBuffer = templateContentControls(templateBuffer, templateData.cc);
|
|
}
|
|
return templateBuffer;
|
|
}
|
|
|
|
// workers/docx.worker.ts
|
|
async function generateDocx(input) {
|
|
const templateBuffer = Buffer.from(input.templateBuffer);
|
|
const output = await applyTemplate(
|
|
templateBuffer,
|
|
input.templateData,
|
|
input.secret,
|
|
input.privateKeyHex,
|
|
input.ticketSec
|
|
);
|
|
return Buffer.from(output);
|
|
}
|
|
export {
|
|
generateDocx as default
|
|
};
|