signing-enclave
Version:
A code signing and notarization tool for macOS and Windows applications. Uses Azure Key Vault for secret and certificate management.
329 lines • 12 kB
JavaScript
// src/receiptWatcher.ts
import fs from "fs-extra";
import path from "path";
import { EventEmitter } from "events";
export const receiptWatcher = (someFolderPath) => {
const receiptFolder = someFolderPath;
const emitter = new EventEmitter();
let watcher = null;
// Helper function to parse receipt
const parseReceipt = async (receiptPath) => {
try {
const receipt = await fs.readJson(receiptPath);
return receipt;
}
catch (error) {
console.error(`Error parsing receipt at ${receiptPath}:`, error);
return null;
}
};
// Helper function to emit events based on receipt changes
const handleReceiptChange = async (receiptPath) => {
const receipt = await parseReceipt(receiptPath);
if (!receipt)
return;
// Determine the event based on receipt status changes
switch (receipt.status) {
case "In Progress":
emitter.emit("file-added", receipt);
emitter.emit("*", { type: "file-added", receipt });
break;
case "Completed":
emitter.emit("file-completed", receipt);
emitter.emit("*", { type: "file-completed", receipt });
break;
case "Failed":
emitter.emit("file-completed", receipt); // Assuming failure is part of completion
emitter.emit("*", { type: "file-completed", receipt });
break;
case "Notarizing":
emitter.emit("file-signed", receipt);
emitter.emit("*", { type: "file-signed", receipt });
break;
// Add more cases as needed
default:
// Emit wildcard for any other status
emitter.emit("*", { type: "unknown-status", receipt });
break;
}
// Check if all files are signed
const receipts = await getReceipts();
const allSigned = receipts.every((r) => r.signingStatus === "Completed");
if (allSigned) {
emitter.emit("all-files-signed", receipts);
emitter.emit("*", { type: "all-files-signed", receipts });
}
// Check if all files are completed
const allCompleted = receipts.every((r) => r.status === "Completed" ||
(os.platform() === "darwin"
? r.status === "Completed"
: r.status === "Completed"));
if (allCompleted) {
emitter.emit("all-files-completed", receipts);
emitter.emit("*", { type: "all-files-completed", receipts });
}
};
// Function to add a file and wait for receipt
const addFile = async (someFilePath) => {
const fileName = path.basename(someFilePath);
const destinationPath = path.join(receiptFolder, fileName);
// Copy the file to the watcher directory
await fs.copy(someFilePath, destinationPath);
// Wait for the receipt to be created
const receiptFilePath = `${destinationPath}.receipt.json`;
const timeout = 5000; // 5 seconds
const receipt = await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Receipt not found for file: ${fileName} after ${timeout} ms`));
}, timeout);
// Listen for 'file-added' event
const onFileAdded = (addedReceipt) => {
if (addedReceipt.originalFile === fileName) {
clearTimeout(timer);
emitter.removeListener("file-added", onFileAdded);
resolve(addedReceipt);
}
};
emitter.on("file-added", onFileAdded);
});
return receipt;
};
// Function to start watching receipts
const start = async () => {
if (watcher) {
console.warn("Receipt watcher is already running.");
return;
}
watcher = fs.watch(receiptFolder, { persistent: true }, async (eventType, filename) => {
if (filename &&
filename.endsWith(".receipt.json") &&
eventType === "change") {
const receiptPath = path.join(receiptFolder, filename);
await handleReceiptChange(receiptPath);
}
});
watcher.on("error", (error) => {
console.error("Watcher error:", error);
});
};
// Function to stop watching receipts
const stop = async () => {
if (watcher) {
watcher.close();
watcher = null;
console.log("Receipt watcher stopped.");
}
else {
console.warn("Receipt watcher is not running.");
}
};
// Function to get all receipts
const getReceipts = async () => {
const files = await fs.readdir(receiptFolder);
const receiptFiles = files.filter((file) => file.endsWith(".receipt.json"));
const receipts = [];
for (const file of receiptFiles) {
const receiptPath = path.join(receiptFolder, file);
const receipt = await parseReceipt(receiptPath);
if (receipt) {
receipts.push(receipt);
}
}
return receipts;
};
// Function to get a specific receipt by filename or id
const getReceipt = async (identifier) => {
const receipts = await getReceipts();
const receipt = receipts.find((r) => r.id === identifier || r.originalFile === identifier);
if (!receipt) {
throw new Error(`Receipt not found for identifier: ${identifier}`);
}
return receipt;
};
// Function to check if a file is signed
const isFileSigned = async (identifier) => {
const receipt = await getReceipt(identifier);
if (receipt.signingStatus === "Completed") {
return true;
}
else if (receipt.signingStatus === "Failed") {
throw new Error(receipt.signingError || "Signing failed.");
}
else {
return false;
}
};
// Function to check if a file is completed
const isFileCompleted = async (identifier) => {
const receipt = await getReceipt(identifier);
if (receipt.status === "Completed") {
return true;
}
else if (receipt.status === "Failed") {
throw new Error(receipt.error || "Processing failed.");
}
else {
return false;
}
};
// Function to check if all files are signed
const allFilesSigned = async () => {
const receipts = await getReceipts();
const notSigned = receipts.filter((r) => r.signingStatus !== "Completed");
if (notSigned.length === 0) {
return true;
}
else {
const errors = notSigned
.filter((r) => r.signingError)
.map((r) => `${r.originalFile}: ${r.signingError}`)
.join("; ");
if (errors) {
throw new Error(errors);
}
return false;
}
};
// Function to check if all files are completed
const allFilesCompleted = async () => {
const receipts = await getReceipts();
const notCompleted = receipts.filter((r) => r.status !== "Completed");
if (notCompleted.length === 0) {
return true;
}
else {
const errors = notCompleted
.filter((r) => r.error)
.map((r) => `${r.originalFile}: ${r.error}`)
.join("; ");
if (errors) {
throw new Error(errors);
}
return false;
}
};
// Function to wait until all files are signed
const whenAllFilesSigned = async () => {
return new Promise((resolve, reject) => {
const checkSigned = async () => {
try {
const signed = await allFilesSigned();
if (signed) {
resolve();
}
}
catch (error) {
reject(error);
}
};
// Initial check
checkSigned();
// Listen for 'all-files-signed' event
const onAllSigned = () => {
resolve();
};
emitter.once("all-files-signed", onAllSigned);
// Timeout after a certain period (e.g., 60 seconds)
const timeout = setTimeout(() => {
emitter.removeListener("all-files-signed", onAllSigned);
reject(new Error("Timeout waiting for all files to be signed."));
}, 60000);
emitter.once("all-files-signed", () => {
clearTimeout(timeout);
});
});
};
// Function to wait until all files are completed
const whenAllFilesCompleted = async () => {
return new Promise((resolve, reject) => {
const checkCompleted = async () => {
try {
const completed = await allFilesCompleted();
if (completed) {
resolve();
}
}
catch (error) {
reject(error);
}
};
// Initial check
checkCompleted();
// Listen for 'all-files-completed' event
const onAllCompleted = () => {
resolve();
};
emitter.once("all-files-completed", onAllCompleted);
// Timeout after a certain period (e.g., 120 seconds)
const timeout = setTimeout(() => {
emitter.removeListener("all-files-completed", onAllCompleted);
reject(new Error("Timeout waiting for all files to be completed."));
}, 120000);
emitter.once("all-files-completed", () => {
clearTimeout(timeout);
});
});
};
// Function to cleanup receipts and files
const cleanup = async () => {
try {
await fs.emptyDir(receiptFolder);
console.log("All receipts and files have been deleted.");
}
catch (error) {
console.error("Error during cleanup:", error);
}
};
// Function to handle events
const on = (event, callback) => {
emitter.on(event, callback);
};
// Function to start the watcher
const start = async () => {
if (watcher) {
console.warn("Receipt watcher is already running.");
return;
}
watcher = fs.watch(receiptFolder, { persistent: true }, async (eventType, filename) => {
if (filename &&
filename.endsWith(".receipt.json") &&
eventType === "change") {
const receiptPath = path.join(receiptFolder, filename);
await handleReceiptChange(receiptPath);
}
});
watcher.on("error", (error) => {
console.error("Watcher error:", error);
});
console.log("Receipt watcher started.");
};
// Function to stop the watcher
const stop = async () => {
if (watcher) {
watcher.close();
watcher = null;
console.log("Receipt watcher stopped.");
}
else {
console.warn("Receipt watcher is not running.");
}
};
// Initialize watcher variable
let watcher = null;
return {
addFile,
start,
getReceipts,
getReceipt,
isFileSigned,
isFileCompleted,
allFilesSigned,
allFilesCompleted,
whenAllFilesSigned,
whenAllFilesCompleted,
stop,
cleanup,
on,
};
};
//# sourceMappingURL=receiptWatcher.js.map