UNPKG

@blocklet/uploader-server

Version:

blocklet upload server

264 lines (263 loc) 9.65 kB
"use strict"; 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 }); }