@blocklet/uploader-server
Version:
blocklet upload server
264 lines (263 loc) • 9.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.initDynamicResourceMiddleware = initDynamicResourceMiddleware;
var _path = require("path");
var _fs = require("fs");
var _config = _interopRequireDefault(require("@blocklet/sdk/lib/config"));
var _utils = require("../utils");
var _glob = require("glob");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function initDynamicResourceMiddleware(options) {
if (!options.resourcePaths || !options.resourcePaths.length) {
throw new Error("resourcePaths is required");
}
const dynamicResourceMap = /* @__PURE__ */new Map();
const directoryPathConfigMap = /* @__PURE__ */new Map();
let watchers = {};
const debounceMap = /* @__PURE__ */new Map();
const DEBOUNCE_TIME = 300;
const cacheOptions = {
maxAge: "365d",
immutable: true,
...options.cacheOptions
};
const {
cacheControl,
cacheControlImmutable
} = (0, _utils.calculateCacheControl)(cacheOptions.maxAge, cacheOptions.immutable);
function shouldIncludeFile(filename, pathConfig) {
if (pathConfig.whitelist?.length && !pathConfig.whitelist.some(ext => filename.endsWith(ext))) {
return false;
}
if (pathConfig.blacklist?.length && pathConfig.blacklist.some(ext => filename.endsWith(ext))) {
return false;
}
return true;
}
function watchDirectory(directory, pathConfig, isParent = false) {
if (watchers[directory]) {
return watchers[directory];
}
try {
const watchOptions = {
persistent: options.watchOptions?.persistent !== false,
recursive: options.watchOptions?.depth !== void 0 ? false : true
};
directoryPathConfigMap.set(directory, pathConfig);
const watcher = (0, _fs.watch)(directory, watchOptions, (eventType, filename) => {
if (!filename) return;
if (options.watchOptions?.ignorePatterns?.some(pattern => filename.startsWith(pattern) || new RegExp(pattern).test(filename))) {
return;
}
const fullPath = (0, _path.join)(directory, filename);
if (isParent && eventType === "rename" && (0, _fs.existsSync)(fullPath)) {
try {
const stat = (0, _fs.statSync)(fullPath);
if (stat.isDirectory()) {
const dirPattern = pathConfig.path.substring(pathConfig.path.indexOf("*"));
const regex = new RegExp(dirPattern.replace(/\*/g, ".*"));
if (regex.test(fullPath)) {
watchDirectory(fullPath, pathConfig);
if (debounceMap.has("scan")) {
clearTimeout(debounceMap.get("scan"));
}
debounceMap.set("scan", setTimeout(() => {
scanDirectories();
debounceMap.delete("scan");
}, DEBOUNCE_TIME));
}
}
} catch (err) {}
return;
}
if (eventType === "change" || eventType === "rename") {
const baseName = (0, _path.basename)(filename);
const debounceKey = `${directory}:${baseName}`;
if (debounceMap.has(debounceKey)) {
clearTimeout(debounceMap.get(debounceKey));
}
debounceMap.set(debounceKey, setTimeout(() => {
processFileChange(directory, baseName, fullPath, eventType, pathConfig);
debounceMap.delete(debounceKey);
}, DEBOUNCE_TIME));
}
});
watchers[directory] = watcher;
return watcher;
} catch (err) {
_utils.logger.error(`Error watching directory ${directory}:`, err);
return null;
}
}
function processFileChange(directory, baseName, fullPath, eventType, pathConfig) {
if ((0, _fs.existsSync)(fullPath)) {
try {
const stat = (0, _fs.statSync)(fullPath);
if (stat.isDirectory()) return;
if (!shouldIncludeFile(baseName, pathConfig)) {
return;
}
try {
const resourceFile = (0, _utils.scanDirectory)(directory, {
whitelist: pathConfig.whitelist,
blacklist: pathConfig.blacklist,
originDir: directory
}).get(baseName);
if (resourceFile) {
let shouldAdd = true;
if (dynamicResourceMap.has(baseName) && options.conflictResolution) {
switch (options.conflictResolution) {
case "last-match":
break;
case "error":
_utils.logger.error(`Resource conflict: ${baseName} exists in multiple directories`);
break;
case "first-match":
default:
shouldAdd = false;
break;
}
}
if (shouldAdd) {
dynamicResourceMap.set(baseName, resourceFile);
if (options.onFileChange) {
options.onFileChange(fullPath, eventType);
}
_utils.logger.debug(`Updated resource: ${baseName}`);
}
}
} catch (err) {
_utils.logger.debug(`Error updating resource for ${fullPath}:`, err);
}
} catch (err) {
_utils.logger.debug(`Error handling file change for ${fullPath}:`, err);
}
} else {
if (dynamicResourceMap.has(baseName)) {
dynamicResourceMap.delete(baseName);
if (options.onFileChange) {
options.onFileChange(fullPath, "delete");
}
_utils.logger.debug(`Removed resource: ${baseName}`);
}
}
}
async function scanDirectories() {
const initialSize = dynamicResourceMap.size;
for (const pathConfig of options.resourcePaths) {
try {
let directories = [];
if (pathConfig.path.includes("*")) {
try {
const pattern = pathConfig.path;
const parentDir = pathConfig.path.substring(0, pathConfig.path.indexOf("*")).replace(/\/+$/, "");
directories = (0, _glob.globSync)(pattern).filter(dir => {
try {
return (0, _fs.existsSync)(dir) && (0, _fs.statSync)(dir).isDirectory();
} catch (err) {
return false;
}
});
try {
watchDirectory(parentDir, pathConfig, true);
} catch (err) {
_utils.logger.debug(`Error watching parent directory ${parentDir}:`, err);
}
} catch (err) {
_utils.logger.error(`Error finding directories for pattern ${pathConfig.path}:`, err);
const plainPath = pathConfig.path.replace(/\*/g, "");
if ((0, _fs.existsSync)(plainPath)) {
directories.push(plainPath);
}
}
} else {
if ((0, _fs.existsSync)(pathConfig.path)) {
directories.push(pathConfig.path);
}
}
let totalResources = 0;
for (const directory of directories) {
watchDirectory(directory, pathConfig);
const dirMap = (0, _utils.scanDirectory)(directory, {
whitelist: pathConfig.whitelist,
blacklist: pathConfig.blacklist,
originDir: directory
});
if (dirMap.size > 0) {
Array.from(dirMap.entries()).forEach(([key, value]) => {
if (dynamicResourceMap.has(key)) {
switch (options.conflictResolution) {
case "last-match":
dynamicResourceMap.set(key, value);
break;
case "error":
_utils.logger.error(`Resource conflict: ${key} exists in multiple directories`);
break;
case "first-match":
default:
break;
}
} else {
dynamicResourceMap.set(key, value);
totalResources++;
}
});
}
}
if (totalResources > 0) {
_utils.logger.info(`Added ${totalResources} resources from ${pathConfig.path} pattern`);
}
} catch (err) {
_utils.logger.error(`Error scanning directories for path ${pathConfig.path}:`, err);
}
}
if ((dynamicResourceMap.size !== initialSize || initialSize === 0) && options.onReady) {
options.onReady(dynamicResourceMap.size);
}
return dynamicResourceMap;
}
function cleanup() {
debounceMap.forEach(timer => clearTimeout(timer));
debounceMap.clear();
for (const key in watchers) {
try {
watchers[key].close();
} catch (err) {}
}
watchers = {};
dynamicResourceMap.clear();
directoryPathConfigMap.clear();
_utils.logger.debug("Dynamic resource middleware cleaned up");
}
if (options.componentDid && _config.default.env.componentDid !== options.componentDid) {
const emptyMiddleware = (req, res, next) => next();
return Object.assign(emptyMiddleware, {
cleanup
});
}
scanDirectories();
const middleware = (req, res, next) => {
const fileName = (0, _utils.getFileNameFromReq)(req);
try {
const resource = dynamicResourceMap.get(fileName);
if (resource) {
(0, _utils.serveResource)(req, res, next, resource, {
...cacheOptions,
setHeaders: options.setHeaders,
cacheControl,
cacheControlImmutable
});
} else {
next();
}
} catch (error) {
_utils.logger.error("Error serving dynamic resource:", error);
next();
}
};
return Object.assign(middleware, {
cleanup
});
}