@blocklet/uploader-server 0.1.91 → 0.1.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ export type DynamicResourcePath = {
2
+ path: string;
3
+ blacklist?: string[];
4
+ whitelist?: string[];
5
+ };
6
+ export type DynamicResourceOptions = {
7
+ componentDid?: string;
8
+ resourcePaths: DynamicResourcePath[];
9
+ watchOptions?: {
10
+ ignorePatterns?: string[];
11
+ persistent?: boolean;
12
+ usePolling?: boolean;
13
+ depth?: number;
14
+ };
15
+ cacheOptions?: {
16
+ maxAge?: string | number;
17
+ immutable?: boolean;
18
+ etag?: boolean;
19
+ lastModified?: boolean;
20
+ };
21
+ onFileChange?: (filePath: string, event: string) => void;
22
+ onReady?: (resourceCount: number) => void;
23
+ setHeaders?: (res: any, filePath: string, stat: any) => void;
24
+ conflictResolution?: 'first-match' | 'last-match' | 'error';
25
+ };
26
+ /**
27
+ * 创建动态资源中间件
28
+ */
29
+ export declare function initDynamicResourceMiddleware(options: DynamicResourceOptions): ((req: any, res: any, next: Function) => any) & {
30
+ cleanup: () => void;
31
+ };
@@ -0,0 +1,266 @@
1
+ import { join, basename } from "path";
2
+ import { watch, existsSync, statSync } from "fs";
3
+ import config from "@blocklet/sdk/lib/config";
4
+ import {
5
+ logger,
6
+ calculateCacheControl,
7
+ serveResource,
8
+ scanDirectory,
9
+ getFileNameFromReq
10
+ } from "../utils.js";
11
+ import { globSync } from "glob";
12
+ export function initDynamicResourceMiddleware(options) {
13
+ if (!options.resourcePaths || !options.resourcePaths.length) {
14
+ throw new Error("resourcePaths is required");
15
+ }
16
+ const dynamicResourceMap = /* @__PURE__ */ new Map();
17
+ const directoryPathConfigMap = /* @__PURE__ */ new Map();
18
+ let watchers = {};
19
+ const debounceMap = /* @__PURE__ */ new Map();
20
+ const DEBOUNCE_TIME = 300;
21
+ const cacheOptions = {
22
+ maxAge: "365d",
23
+ immutable: true,
24
+ ...options.cacheOptions
25
+ };
26
+ const { cacheControl, cacheControlImmutable } = calculateCacheControl(cacheOptions.maxAge, cacheOptions.immutable);
27
+ function shouldIncludeFile(filename, pathConfig) {
28
+ if (pathConfig.whitelist?.length && !pathConfig.whitelist.some((ext) => filename.endsWith(ext))) {
29
+ return false;
30
+ }
31
+ if (pathConfig.blacklist?.length && pathConfig.blacklist.some((ext) => filename.endsWith(ext))) {
32
+ return false;
33
+ }
34
+ return true;
35
+ }
36
+ function watchDirectory(directory, pathConfig, isParent = false) {
37
+ if (watchers[directory]) {
38
+ return watchers[directory];
39
+ }
40
+ try {
41
+ const watchOptions = {
42
+ persistent: options.watchOptions?.persistent !== false,
43
+ recursive: options.watchOptions?.depth !== void 0 ? false : true
44
+ };
45
+ directoryPathConfigMap.set(directory, pathConfig);
46
+ const watcher = watch(directory, watchOptions, (eventType, filename) => {
47
+ if (!filename) return;
48
+ if (options.watchOptions?.ignorePatterns?.some(
49
+ (pattern) => filename.startsWith(pattern) || new RegExp(pattern).test(filename)
50
+ )) {
51
+ return;
52
+ }
53
+ const fullPath = join(directory, filename);
54
+ if (isParent && eventType === "rename" && existsSync(fullPath)) {
55
+ try {
56
+ const stat = statSync(fullPath);
57
+ if (stat.isDirectory()) {
58
+ const dirPattern = pathConfig.path.substring(pathConfig.path.indexOf("*"));
59
+ const regex = new RegExp(dirPattern.replace(/\*/g, ".*"));
60
+ if (regex.test(fullPath)) {
61
+ watchDirectory(fullPath, pathConfig);
62
+ if (debounceMap.has("scan")) {
63
+ clearTimeout(debounceMap.get("scan"));
64
+ }
65
+ debounceMap.set(
66
+ "scan",
67
+ setTimeout(() => {
68
+ scanDirectories();
69
+ debounceMap.delete("scan");
70
+ }, DEBOUNCE_TIME)
71
+ );
72
+ }
73
+ }
74
+ } catch (err) {
75
+ }
76
+ return;
77
+ }
78
+ if (eventType === "change" || eventType === "rename") {
79
+ const baseName = basename(filename);
80
+ const debounceKey = `${directory}:${baseName}`;
81
+ if (debounceMap.has(debounceKey)) {
82
+ clearTimeout(debounceMap.get(debounceKey));
83
+ }
84
+ debounceMap.set(
85
+ debounceKey,
86
+ setTimeout(() => {
87
+ processFileChange(directory, baseName, fullPath, eventType, pathConfig);
88
+ debounceMap.delete(debounceKey);
89
+ }, DEBOUNCE_TIME)
90
+ );
91
+ }
92
+ });
93
+ watchers[directory] = watcher;
94
+ return watcher;
95
+ } catch (err) {
96
+ logger.error(`Error watching directory ${directory}:`, err);
97
+ return null;
98
+ }
99
+ }
100
+ function processFileChange(directory, baseName, fullPath, eventType, pathConfig) {
101
+ if (existsSync(fullPath)) {
102
+ try {
103
+ const stat = statSync(fullPath);
104
+ if (stat.isDirectory()) return;
105
+ if (!shouldIncludeFile(baseName, pathConfig)) {
106
+ return;
107
+ }
108
+ try {
109
+ const resourceFile = scanDirectory(directory, {
110
+ whitelist: pathConfig.whitelist,
111
+ blacklist: pathConfig.blacklist,
112
+ originDir: directory
113
+ }).get(baseName);
114
+ if (resourceFile) {
115
+ let shouldAdd = true;
116
+ if (dynamicResourceMap.has(baseName) && options.conflictResolution) {
117
+ switch (options.conflictResolution) {
118
+ case "last-match":
119
+ break;
120
+ case "error":
121
+ logger.error(`Resource conflict: ${baseName} exists in multiple directories`);
122
+ break;
123
+ case "first-match":
124
+ default:
125
+ shouldAdd = false;
126
+ break;
127
+ }
128
+ }
129
+ if (shouldAdd) {
130
+ dynamicResourceMap.set(baseName, resourceFile);
131
+ if (options.onFileChange) {
132
+ options.onFileChange(fullPath, eventType);
133
+ }
134
+ logger.debug(`Updated resource: ${baseName}`);
135
+ }
136
+ }
137
+ } catch (err) {
138
+ logger.debug(`Error updating resource for ${fullPath}:`, err);
139
+ }
140
+ } catch (err) {
141
+ logger.debug(`Error handling file change for ${fullPath}:`, err);
142
+ }
143
+ } else {
144
+ if (dynamicResourceMap.has(baseName)) {
145
+ dynamicResourceMap.delete(baseName);
146
+ if (options.onFileChange) {
147
+ options.onFileChange(fullPath, "delete");
148
+ }
149
+ logger.debug(`Removed resource: ${baseName}`);
150
+ }
151
+ }
152
+ }
153
+ async function scanDirectories() {
154
+ const initialSize = dynamicResourceMap.size;
155
+ for (const pathConfig of options.resourcePaths) {
156
+ try {
157
+ let directories = [];
158
+ if (pathConfig.path.includes("*")) {
159
+ try {
160
+ const pattern = pathConfig.path;
161
+ const parentDir = pathConfig.path.substring(0, pathConfig.path.indexOf("*")).replace(/\/+$/, "");
162
+ directories = globSync(pattern).filter((dir) => {
163
+ try {
164
+ return existsSync(dir) && statSync(dir).isDirectory();
165
+ } catch (err) {
166
+ return false;
167
+ }
168
+ });
169
+ try {
170
+ watchDirectory(parentDir, pathConfig, true);
171
+ } catch (err) {
172
+ logger.debug(`Error watching parent directory ${parentDir}:`, err);
173
+ }
174
+ } catch (err) {
175
+ logger.error(`Error finding directories for pattern ${pathConfig.path}:`, err);
176
+ const plainPath = pathConfig.path.replace(/\*/g, "");
177
+ if (existsSync(plainPath)) {
178
+ directories.push(plainPath);
179
+ }
180
+ }
181
+ } else {
182
+ if (existsSync(pathConfig.path)) {
183
+ directories.push(pathConfig.path);
184
+ }
185
+ }
186
+ let totalResources = 0;
187
+ for (const directory of directories) {
188
+ watchDirectory(directory, pathConfig);
189
+ const dirMap = scanDirectory(directory, {
190
+ whitelist: pathConfig.whitelist,
191
+ blacklist: pathConfig.blacklist,
192
+ originDir: directory
193
+ });
194
+ if (dirMap.size > 0) {
195
+ Array.from(dirMap.entries()).forEach(([key, value]) => {
196
+ if (dynamicResourceMap.has(key)) {
197
+ switch (options.conflictResolution) {
198
+ case "last-match":
199
+ dynamicResourceMap.set(key, value);
200
+ break;
201
+ case "error":
202
+ logger.error(`Resource conflict: ${key} exists in multiple directories`);
203
+ break;
204
+ case "first-match":
205
+ default:
206
+ break;
207
+ }
208
+ } else {
209
+ dynamicResourceMap.set(key, value);
210
+ totalResources++;
211
+ }
212
+ });
213
+ }
214
+ }
215
+ if (totalResources > 0) {
216
+ logger.info(`Added ${totalResources} resources from ${pathConfig.path} pattern`);
217
+ }
218
+ } catch (err) {
219
+ logger.error(`Error scanning directories for path ${pathConfig.path}:`, err);
220
+ }
221
+ }
222
+ if ((dynamicResourceMap.size !== initialSize || initialSize === 0) && options.onReady) {
223
+ options.onReady(dynamicResourceMap.size);
224
+ }
225
+ return dynamicResourceMap;
226
+ }
227
+ function cleanup() {
228
+ debounceMap.forEach((timer) => clearTimeout(timer));
229
+ debounceMap.clear();
230
+ for (const key in watchers) {
231
+ try {
232
+ watchers[key].close();
233
+ } catch (err) {
234
+ }
235
+ }
236
+ watchers = {};
237
+ dynamicResourceMap.clear();
238
+ directoryPathConfigMap.clear();
239
+ logger.debug("Dynamic resource middleware cleaned up");
240
+ }
241
+ if (options.componentDid && config.env.componentDid !== options.componentDid) {
242
+ const emptyMiddleware = (req, res, next) => next();
243
+ return Object.assign(emptyMiddleware, { cleanup });
244
+ }
245
+ scanDirectories();
246
+ const middleware = (req, res, next) => {
247
+ const fileName = getFileNameFromReq(req);
248
+ try {
249
+ const resource = dynamicResourceMap.get(fileName);
250
+ if (resource) {
251
+ serveResource(req, res, next, resource, {
252
+ ...cacheOptions,
253
+ setHeaders: options.setHeaders,
254
+ cacheControl,
255
+ cacheControlImmutable
256
+ });
257
+ } else {
258
+ next();
259
+ }
260
+ } catch (error) {
261
+ logger.error("Error serving dynamic resource:", error);
262
+ next();
263
+ }
264
+ };
265
+ return Object.assign(middleware, { cleanup });
266
+ }
@@ -1,13 +1,18 @@
1
- import { existsSync, readdirSync, statSync, createReadStream } from "fs";
1
+ import { existsSync } from "fs";
2
2
  import { join, basename } from "path";
3
3
  import config from "@blocklet/sdk/lib/config";
4
4
  import { getResources } from "@blocklet/sdk/lib/component";
5
5
  import joinUrl from "url-join";
6
6
  import component from "@blocklet/sdk/lib/component";
7
- import mime from "mime-types";
8
- import { setPDFDownloadHeader, logger } from "../utils.js";
7
+ import {
8
+ setPDFDownloadHeader,
9
+ logger,
10
+ calculateCacheControl,
11
+ serveResource,
12
+ scanDirectory,
13
+ getFileNameFromReq
14
+ } from "../utils.js";
9
15
  import { ImageBinDid } from "../constants.js";
10
- import ms from "ms";
11
16
  const ImgResourceType = "imgpack";
12
17
  let skipRunningCheck = false;
13
18
  let resourceTypes = [
@@ -46,34 +51,14 @@ export const mappingResource = async () => {
46
51
  const { dir, whitelist, blacklist, originDir, blockletInfo } = resource;
47
52
  if (existsSync(dir)) {
48
53
  try {
49
- const files = readdirSync(dir);
50
- for (const file of files) {
51
- const filePath = join(dir, file);
52
- let stat;
53
- try {
54
- stat = statSync(filePath);
55
- if (stat.isDirectory()) continue;
56
- } catch (e) {
57
- continue;
58
- }
59
- if (whitelist?.length && !whitelist.some((ext) => file.endsWith(ext))) {
60
- continue;
61
- }
62
- if (blacklist?.length && blacklist.some((ext) => file.endsWith(ext))) {
63
- continue;
64
- }
65
- const contentType = mime.lookup(filePath) || "application/octet-stream";
66
- resourcesMap.set(file, {
67
- filePath,
68
- dir,
69
- originDir,
70
- blockletInfo,
71
- whitelist,
72
- blacklist,
73
- mtime: stat.mtime,
74
- size: stat.size,
75
- contentType
76
- });
54
+ const dirResourceMap = scanDirectory(dir, {
55
+ whitelist,
56
+ blacklist,
57
+ originDir,
58
+ blockletInfo
59
+ });
60
+ for (const [key, value] of dirResourceMap.entries()) {
61
+ resourcesMap.set(key, value);
77
62
  }
78
63
  } catch (err) {
79
64
  logger.error(`Error scanning directory ${dir}:`, err);
@@ -100,20 +85,10 @@ export const initStaticResourceMiddleware = ({
100
85
  skipRunningCheck: _skipRunningCheck
101
86
  } = {}) => {
102
87
  skipRunningCheck = !!_skipRunningCheck;
103
- let maxAgeInSeconds = 31536e3;
104
- const maxAge = options.maxAge || "365d";
105
- if (typeof maxAge === "string") {
106
- try {
107
- const milliseconds = ms(maxAge);
108
- maxAgeInSeconds = typeof milliseconds === "number" ? milliseconds / 1e3 : 31536e3;
109
- } catch (e) {
110
- logger.warn(`Invalid maxAge format: ${maxAge}, using default 1 year (31536000 seconds)`);
111
- }
112
- } else {
113
- maxAgeInSeconds = maxAge;
114
- }
115
- const cacheControl = `public, max-age=${maxAgeInSeconds}`;
116
- const cacheControlImmutable = `${cacheControl}, immutable`;
88
+ const { cacheControl, cacheControlImmutable } = calculateCacheControl(
89
+ options.maxAge || "365d",
90
+ options.immutable !== false
91
+ );
117
92
  if (_resourceTypes?.length > 0) {
118
93
  resourceTypes = _resourceTypes.map((item) => {
119
94
  if (typeof item === "string") {
@@ -130,33 +105,15 @@ export const initStaticResourceMiddleware = ({
130
105
  }
131
106
  mappingResource();
132
107
  return (req, res, next) => {
133
- const fileName = basename(req.path || req.url?.split("?")[0]);
108
+ const fileName = getFileNameFromReq(req);
134
109
  try {
135
110
  const resource = resourcesMap.get(fileName);
136
111
  if (resource) {
137
- res.setHeader("Content-Type", resource.contentType);
138
- res.setHeader("Content-Length", resource.size);
139
- res.setHeader("Last-Modified", resource.mtime.toUTCString());
140
- res.setHeader("Cache-Control", options.immutable === false ? cacheControl : cacheControlImmutable);
141
- if (options.setHeaders && typeof options.setHeaders === "function") {
142
- const statObj = { mtime: resource.mtime, size: resource.size };
143
- options.setHeaders(res, resource.filePath, statObj);
144
- }
145
- const ifModifiedSince = req.headers["if-modified-since"];
146
- if (ifModifiedSince) {
147
- const ifModifiedSinceDate = new Date(ifModifiedSince);
148
- if (resource.mtime <= ifModifiedSinceDate) {
149
- res.statusCode = 304;
150
- res.end();
151
- return;
152
- }
153
- }
154
- const fileStream = createReadStream(resource.filePath);
155
- fileStream.on("error", (error) => {
156
- logger.error(`Error streaming file ${resource.filePath}:`, error);
157
- next(error);
112
+ serveResource(req, res, next, resource, {
113
+ ...options,
114
+ cacheControl,
115
+ cacheControlImmutable
158
116
  });
159
- fileStream.pipe(res);
160
117
  } else {
161
118
  next();
162
119
  }
@@ -1,3 +1,4 @@
1
1
  export * from './middlewares/local-storage';
2
2
  export * from './middlewares/companion';
3
3
  export * from './middlewares/static-resource';
4
+ export * from './middlewares/dynamic-resource';
package/es/middlewares.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./middlewares/local-storage.js";
2
2
  export * from "./middlewares/companion.js";
3
3
  export * from "./middlewares/static-resource.js";
4
+ export * from "./middlewares/dynamic-resource.js";
package/es/utils.d.ts CHANGED
@@ -25,4 +25,28 @@ export declare function uploadToMediaKit({ filePath, fileName, base64, extraComp
25
25
  extraComponentCallOptions?: CallComponentOptions;
26
26
  }): Promise<import("axios").AxiosResponse<import("http").IncomingMessage, any> | undefined>;
27
27
  export declare function getMediaKitFileStream(filePath: string): Promise<import("axios").AxiosResponse<import("http").IncomingMessage, any>>;
28
+ export type ResourceFile = {
29
+ filePath: string;
30
+ dir: string;
31
+ originDir: string;
32
+ blockletInfo: any;
33
+ whitelist?: string[];
34
+ blacklist?: string[];
35
+ mtime: Date;
36
+ size: number;
37
+ contentType: string;
38
+ };
39
+ export declare function calculateCacheControl(maxAge?: string | number, immutable?: boolean): {
40
+ cacheControl: string;
41
+ cacheControlImmutable: string;
42
+ maxAgeInSeconds: number;
43
+ };
44
+ export declare function serveResource(req: any, res: any, next: Function, resource: ResourceFile, options?: any): void;
45
+ export declare function scanDirectory(directory: string, options?: {
46
+ whitelist?: string[];
47
+ blacklist?: string[];
48
+ originDir?: string;
49
+ blockletInfo?: any;
50
+ }): Map<string, ResourceFile>;
51
+ export declare function getFileNameFromReq(req: any): any;
28
52
  export {};
package/es/utils.js CHANGED
@@ -9,6 +9,10 @@ import crypto from "crypto";
9
9
  import { getSignData } from "@blocklet/sdk/lib/util/verify-sign";
10
10
  import FormData from "form-data";
11
11
  import omit from "lodash/omit";
12
+ import ms from "ms";
13
+ import mime from "mime-types";
14
+ import { existsSync, readdirSync, statSync } from "fs";
15
+ import { join } from "path";
12
16
  export let logger = console;
13
17
  if (process.env.BLOCKLET_LOG_DIR) {
14
18
  try {
@@ -175,3 +179,101 @@ export async function getMediaKitFileStream(filePath) {
175
179
  });
176
180
  return res;
177
181
  }
182
+ export function calculateCacheControl(maxAge = "365d", immutable = true) {
183
+ let maxAgeInSeconds = 31536e3;
184
+ if (typeof maxAge === "string") {
185
+ try {
186
+ const milliseconds = ms(maxAge);
187
+ maxAgeInSeconds = typeof milliseconds === "number" ? milliseconds / 1e3 : 31536e3;
188
+ } catch (e) {
189
+ logger.warn(`Invalid maxAge format: ${maxAge}, using default 1 year (31536000 seconds)`);
190
+ }
191
+ } else {
192
+ maxAgeInSeconds = maxAge;
193
+ }
194
+ const cacheControl = `public, max-age=${maxAgeInSeconds}`;
195
+ const cacheControlImmutable = `${cacheControl}, immutable`;
196
+ return {
197
+ cacheControl,
198
+ cacheControlImmutable,
199
+ maxAgeInSeconds
200
+ };
201
+ }
202
+ export function serveResource(req, res, next, resource, options = {}) {
203
+ try {
204
+ res.setHeader("Content-Type", resource.contentType);
205
+ res.setHeader("Content-Length", resource.size);
206
+ res.setHeader("Last-Modified", resource.mtime.toUTCString());
207
+ const { cacheControl, cacheControlImmutable } = calculateCacheControl(
208
+ options.maxAge || "365d",
209
+ options.immutable !== false
210
+ );
211
+ res.setHeader("Cache-Control", options.immutable === false ? cacheControl : cacheControlImmutable);
212
+ if (options.setHeaders && typeof options.setHeaders === "function") {
213
+ const statObj = { mtime: resource.mtime, size: resource.size };
214
+ options.setHeaders(res, resource.filePath, statObj);
215
+ }
216
+ const ifModifiedSince = req.headers["if-modified-since"];
217
+ if (ifModifiedSince) {
218
+ const ifModifiedSinceDate = new Date(ifModifiedSince);
219
+ if (resource.mtime <= ifModifiedSinceDate) {
220
+ res.statusCode = 304;
221
+ res.end();
222
+ return;
223
+ }
224
+ }
225
+ const fileStream = createReadStream(resource.filePath);
226
+ fileStream.on("error", (error) => {
227
+ logger.error(`Error streaming file ${resource.filePath}:`, error);
228
+ next(error);
229
+ });
230
+ fileStream.pipe(res);
231
+ } catch (error) {
232
+ logger.error("Error serving static file:", error);
233
+ next(error);
234
+ }
235
+ }
236
+ export function scanDirectory(directory, options = {}) {
237
+ const resourceMap = /* @__PURE__ */ new Map();
238
+ if (!existsSync(directory)) {
239
+ return resourceMap;
240
+ }
241
+ try {
242
+ const files = readdirSync(directory);
243
+ for (const file of files) {
244
+ const filePath = join(directory, file);
245
+ let stat;
246
+ try {
247
+ stat = statSync(filePath);
248
+ if (stat.isDirectory()) continue;
249
+ } catch (e) {
250
+ continue;
251
+ }
252
+ if (options.whitelist?.length && !options.whitelist.some((ext) => file.endsWith(ext))) {
253
+ continue;
254
+ }
255
+ if (options.blacklist?.length && options.blacklist.some((ext) => file.endsWith(ext))) {
256
+ continue;
257
+ }
258
+ const contentType = mime.lookup(filePath) || "application/octet-stream";
259
+ resourceMap.set(file, {
260
+ filePath,
261
+ dir: directory,
262
+ originDir: options.originDir || directory,
263
+ blockletInfo: options.blockletInfo || {},
264
+ whitelist: options.whitelist,
265
+ blacklist: options.blacklist,
266
+ mtime: stat.mtime,
267
+ size: stat.size,
268
+ contentType
269
+ });
270
+ }
271
+ } catch (err) {
272
+ logger.error(`Error scanning directory ${directory}:`, err);
273
+ }
274
+ return resourceMap;
275
+ }
276
+ export function getFileNameFromReq(req) {
277
+ const pathname = req.path || req.url?.split("?")[0];
278
+ return path.basename(decodeURIComponent(pathname || ""));
279
+ }
@@ -0,0 +1,31 @@
1
+ export type DynamicResourcePath = {
2
+ path: string;
3
+ blacklist?: string[];
4
+ whitelist?: string[];
5
+ };
6
+ export type DynamicResourceOptions = {
7
+ componentDid?: string;
8
+ resourcePaths: DynamicResourcePath[];
9
+ watchOptions?: {
10
+ ignorePatterns?: string[];
11
+ persistent?: boolean;
12
+ usePolling?: boolean;
13
+ depth?: number;
14
+ };
15
+ cacheOptions?: {
16
+ maxAge?: string | number;
17
+ immutable?: boolean;
18
+ etag?: boolean;
19
+ lastModified?: boolean;
20
+ };
21
+ onFileChange?: (filePath: string, event: string) => void;
22
+ onReady?: (resourceCount: number) => void;
23
+ setHeaders?: (res: any, filePath: string, stat: any) => void;
24
+ conflictResolution?: 'first-match' | 'last-match' | 'error';
25
+ };
26
+ /**
27
+ * 创建动态资源中间件
28
+ */
29
+ export declare function initDynamicResourceMiddleware(options: DynamicResourceOptions): ((req: any, res: any, next: Function) => any) & {
30
+ cleanup: () => void;
31
+ };
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.initDynamicResourceMiddleware = initDynamicResourceMiddleware;
7
+ var _path = require("path");
8
+ var _fs = require("fs");
9
+ var _config = _interopRequireDefault(require("@blocklet/sdk/lib/config"));
10
+ var _utils = require("../utils");
11
+ var _glob = require("glob");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ function initDynamicResourceMiddleware(options) {
14
+ if (!options.resourcePaths || !options.resourcePaths.length) {
15
+ throw new Error("resourcePaths is required");
16
+ }
17
+ const dynamicResourceMap = /* @__PURE__ */new Map();
18
+ const directoryPathConfigMap = /* @__PURE__ */new Map();
19
+ let watchers = {};
20
+ const debounceMap = /* @__PURE__ */new Map();
21
+ const DEBOUNCE_TIME = 300;
22
+ const cacheOptions = {
23
+ maxAge: "365d",
24
+ immutable: true,
25
+ ...options.cacheOptions
26
+ };
27
+ const {
28
+ cacheControl,
29
+ cacheControlImmutable
30
+ } = (0, _utils.calculateCacheControl)(cacheOptions.maxAge, cacheOptions.immutable);
31
+ function shouldIncludeFile(filename, pathConfig) {
32
+ if (pathConfig.whitelist?.length && !pathConfig.whitelist.some(ext => filename.endsWith(ext))) {
33
+ return false;
34
+ }
35
+ if (pathConfig.blacklist?.length && pathConfig.blacklist.some(ext => filename.endsWith(ext))) {
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ function watchDirectory(directory, pathConfig, isParent = false) {
41
+ if (watchers[directory]) {
42
+ return watchers[directory];
43
+ }
44
+ try {
45
+ const watchOptions = {
46
+ persistent: options.watchOptions?.persistent !== false,
47
+ recursive: options.watchOptions?.depth !== void 0 ? false : true
48
+ };
49
+ directoryPathConfigMap.set(directory, pathConfig);
50
+ const watcher = (0, _fs.watch)(directory, watchOptions, (eventType, filename) => {
51
+ if (!filename) return;
52
+ if (options.watchOptions?.ignorePatterns?.some(pattern => filename.startsWith(pattern) || new RegExp(pattern).test(filename))) {
53
+ return;
54
+ }
55
+ const fullPath = (0, _path.join)(directory, filename);
56
+ if (isParent && eventType === "rename" && (0, _fs.existsSync)(fullPath)) {
57
+ try {
58
+ const stat = (0, _fs.statSync)(fullPath);
59
+ if (stat.isDirectory()) {
60
+ const dirPattern = pathConfig.path.substring(pathConfig.path.indexOf("*"));
61
+ const regex = new RegExp(dirPattern.replace(/\*/g, ".*"));
62
+ if (regex.test(fullPath)) {
63
+ watchDirectory(fullPath, pathConfig);
64
+ if (debounceMap.has("scan")) {
65
+ clearTimeout(debounceMap.get("scan"));
66
+ }
67
+ debounceMap.set("scan", setTimeout(() => {
68
+ scanDirectories();
69
+ debounceMap.delete("scan");
70
+ }, DEBOUNCE_TIME));
71
+ }
72
+ }
73
+ } catch (err) {}
74
+ return;
75
+ }
76
+ if (eventType === "change" || eventType === "rename") {
77
+ const baseName = (0, _path.basename)(filename);
78
+ const debounceKey = `${directory}:${baseName}`;
79
+ if (debounceMap.has(debounceKey)) {
80
+ clearTimeout(debounceMap.get(debounceKey));
81
+ }
82
+ debounceMap.set(debounceKey, setTimeout(() => {
83
+ processFileChange(directory, baseName, fullPath, eventType, pathConfig);
84
+ debounceMap.delete(debounceKey);
85
+ }, DEBOUNCE_TIME));
86
+ }
87
+ });
88
+ watchers[directory] = watcher;
89
+ return watcher;
90
+ } catch (err) {
91
+ _utils.logger.error(`Error watching directory ${directory}:`, err);
92
+ return null;
93
+ }
94
+ }
95
+ function processFileChange(directory, baseName, fullPath, eventType, pathConfig) {
96
+ if ((0, _fs.existsSync)(fullPath)) {
97
+ try {
98
+ const stat = (0, _fs.statSync)(fullPath);
99
+ if (stat.isDirectory()) return;
100
+ if (!shouldIncludeFile(baseName, pathConfig)) {
101
+ return;
102
+ }
103
+ try {
104
+ const resourceFile = (0, _utils.scanDirectory)(directory, {
105
+ whitelist: pathConfig.whitelist,
106
+ blacklist: pathConfig.blacklist,
107
+ originDir: directory
108
+ }).get(baseName);
109
+ if (resourceFile) {
110
+ let shouldAdd = true;
111
+ if (dynamicResourceMap.has(baseName) && options.conflictResolution) {
112
+ switch (options.conflictResolution) {
113
+ case "last-match":
114
+ break;
115
+ case "error":
116
+ _utils.logger.error(`Resource conflict: ${baseName} exists in multiple directories`);
117
+ break;
118
+ case "first-match":
119
+ default:
120
+ shouldAdd = false;
121
+ break;
122
+ }
123
+ }
124
+ if (shouldAdd) {
125
+ dynamicResourceMap.set(baseName, resourceFile);
126
+ if (options.onFileChange) {
127
+ options.onFileChange(fullPath, eventType);
128
+ }
129
+ _utils.logger.debug(`Updated resource: ${baseName}`);
130
+ }
131
+ }
132
+ } catch (err) {
133
+ _utils.logger.debug(`Error updating resource for ${fullPath}:`, err);
134
+ }
135
+ } catch (err) {
136
+ _utils.logger.debug(`Error handling file change for ${fullPath}:`, err);
137
+ }
138
+ } else {
139
+ if (dynamicResourceMap.has(baseName)) {
140
+ dynamicResourceMap.delete(baseName);
141
+ if (options.onFileChange) {
142
+ options.onFileChange(fullPath, "delete");
143
+ }
144
+ _utils.logger.debug(`Removed resource: ${baseName}`);
145
+ }
146
+ }
147
+ }
148
+ async function scanDirectories() {
149
+ const initialSize = dynamicResourceMap.size;
150
+ for (const pathConfig of options.resourcePaths) {
151
+ try {
152
+ let directories = [];
153
+ if (pathConfig.path.includes("*")) {
154
+ try {
155
+ const pattern = pathConfig.path;
156
+ const parentDir = pathConfig.path.substring(0, pathConfig.path.indexOf("*")).replace(/\/+$/, "");
157
+ directories = (0, _glob.globSync)(pattern).filter(dir => {
158
+ try {
159
+ return (0, _fs.existsSync)(dir) && (0, _fs.statSync)(dir).isDirectory();
160
+ } catch (err) {
161
+ return false;
162
+ }
163
+ });
164
+ try {
165
+ watchDirectory(parentDir, pathConfig, true);
166
+ } catch (err) {
167
+ _utils.logger.debug(`Error watching parent directory ${parentDir}:`, err);
168
+ }
169
+ } catch (err) {
170
+ _utils.logger.error(`Error finding directories for pattern ${pathConfig.path}:`, err);
171
+ const plainPath = pathConfig.path.replace(/\*/g, "");
172
+ if ((0, _fs.existsSync)(plainPath)) {
173
+ directories.push(plainPath);
174
+ }
175
+ }
176
+ } else {
177
+ if ((0, _fs.existsSync)(pathConfig.path)) {
178
+ directories.push(pathConfig.path);
179
+ }
180
+ }
181
+ let totalResources = 0;
182
+ for (const directory of directories) {
183
+ watchDirectory(directory, pathConfig);
184
+ const dirMap = (0, _utils.scanDirectory)(directory, {
185
+ whitelist: pathConfig.whitelist,
186
+ blacklist: pathConfig.blacklist,
187
+ originDir: directory
188
+ });
189
+ if (dirMap.size > 0) {
190
+ Array.from(dirMap.entries()).forEach(([key, value]) => {
191
+ if (dynamicResourceMap.has(key)) {
192
+ switch (options.conflictResolution) {
193
+ case "last-match":
194
+ dynamicResourceMap.set(key, value);
195
+ break;
196
+ case "error":
197
+ _utils.logger.error(`Resource conflict: ${key} exists in multiple directories`);
198
+ break;
199
+ case "first-match":
200
+ default:
201
+ break;
202
+ }
203
+ } else {
204
+ dynamicResourceMap.set(key, value);
205
+ totalResources++;
206
+ }
207
+ });
208
+ }
209
+ }
210
+ if (totalResources > 0) {
211
+ _utils.logger.info(`Added ${totalResources} resources from ${pathConfig.path} pattern`);
212
+ }
213
+ } catch (err) {
214
+ _utils.logger.error(`Error scanning directories for path ${pathConfig.path}:`, err);
215
+ }
216
+ }
217
+ if ((dynamicResourceMap.size !== initialSize || initialSize === 0) && options.onReady) {
218
+ options.onReady(dynamicResourceMap.size);
219
+ }
220
+ return dynamicResourceMap;
221
+ }
222
+ function cleanup() {
223
+ debounceMap.forEach(timer => clearTimeout(timer));
224
+ debounceMap.clear();
225
+ for (const key in watchers) {
226
+ try {
227
+ watchers[key].close();
228
+ } catch (err) {}
229
+ }
230
+ watchers = {};
231
+ dynamicResourceMap.clear();
232
+ directoryPathConfigMap.clear();
233
+ _utils.logger.debug("Dynamic resource middleware cleaned up");
234
+ }
235
+ if (options.componentDid && _config.default.env.componentDid !== options.componentDid) {
236
+ const emptyMiddleware = (req, res, next) => next();
237
+ return Object.assign(emptyMiddleware, {
238
+ cleanup
239
+ });
240
+ }
241
+ scanDirectories();
242
+ const middleware = (req, res, next) => {
243
+ const fileName = (0, _utils.getFileNameFromReq)(req);
244
+ try {
245
+ const resource = dynamicResourceMap.get(fileName);
246
+ if (resource) {
247
+ (0, _utils.serveResource)(req, res, next, resource, {
248
+ ...cacheOptions,
249
+ setHeaders: options.setHeaders,
250
+ cacheControl,
251
+ cacheControlImmutable
252
+ });
253
+ } else {
254
+ next();
255
+ }
256
+ } catch (error) {
257
+ _utils.logger.error("Error serving dynamic resource:", error);
258
+ next();
259
+ }
260
+ };
261
+ return Object.assign(middleware, {
262
+ cleanup
263
+ });
264
+ }
@@ -9,10 +9,8 @@ var _path = require("path");
9
9
  var _config = _interopRequireDefault(require("@blocklet/sdk/lib/config"));
10
10
  var _component = _interopRequireWildcard(require("@blocklet/sdk/lib/component"));
11
11
  var _urlJoin = _interopRequireDefault(require("url-join"));
12
- var _mimeTypes = _interopRequireDefault(require("mime-types"));
13
12
  var _utils = require("../utils");
14
13
  var _constants = require("../constants");
15
- var _ms = _interopRequireDefault(require("ms"));
16
14
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
17
15
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
18
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -60,34 +58,14 @@ const mappingResource = async () => {
60
58
  } = resource;
61
59
  if ((0, _fs.existsSync)(dir)) {
62
60
  try {
63
- const files = (0, _fs.readdirSync)(dir);
64
- for (const file of files) {
65
- const filePath = (0, _path.join)(dir, file);
66
- let stat;
67
- try {
68
- stat = (0, _fs.statSync)(filePath);
69
- if (stat.isDirectory()) continue;
70
- } catch (e) {
71
- continue;
72
- }
73
- if (whitelist?.length && !whitelist.some(ext => file.endsWith(ext))) {
74
- continue;
75
- }
76
- if (blacklist?.length && blacklist.some(ext => file.endsWith(ext))) {
77
- continue;
78
- }
79
- const contentType = _mimeTypes.default.lookup(filePath) || "application/octet-stream";
80
- resourcesMap.set(file, {
81
- filePath,
82
- dir,
83
- originDir,
84
- blockletInfo,
85
- whitelist,
86
- blacklist,
87
- mtime: stat.mtime,
88
- size: stat.size,
89
- contentType
90
- });
61
+ const dirResourceMap = (0, _utils.scanDirectory)(dir, {
62
+ whitelist,
63
+ blacklist,
64
+ originDir,
65
+ blockletInfo
66
+ });
67
+ for (const [key, value] of dirResourceMap.entries()) {
68
+ resourcesMap.set(key, value);
91
69
  }
92
70
  } catch (err) {
93
71
  _utils.logger.error(`Error scanning directory ${dir}:`, err);
@@ -118,20 +96,10 @@ const initStaticResourceMiddleware = ({
118
96
  skipRunningCheck: _skipRunningCheck
119
97
  } = {}) => {
120
98
  skipRunningCheck = !!_skipRunningCheck;
121
- let maxAgeInSeconds = 31536e3;
122
- const maxAge = options.maxAge || "365d";
123
- if (typeof maxAge === "string") {
124
- try {
125
- const milliseconds = (0, _ms.default)(maxAge);
126
- maxAgeInSeconds = typeof milliseconds === "number" ? milliseconds / 1e3 : 31536e3;
127
- } catch (e) {
128
- _utils.logger.warn(`Invalid maxAge format: ${maxAge}, using default 1 year (31536000 seconds)`);
129
- }
130
- } else {
131
- maxAgeInSeconds = maxAge;
132
- }
133
- const cacheControl = `public, max-age=${maxAgeInSeconds}`;
134
- const cacheControlImmutable = `${cacheControl}, immutable`;
99
+ const {
100
+ cacheControl,
101
+ cacheControlImmutable
102
+ } = (0, _utils.calculateCacheControl)(options.maxAge || "365d", options.immutable !== false);
135
103
  if (_resourceTypes?.length > 0) {
136
104
  resourceTypes = _resourceTypes.map(item => {
137
105
  if (typeof item === "string") {
@@ -148,36 +116,15 @@ const initStaticResourceMiddleware = ({
148
116
  }
149
117
  mappingResource();
150
118
  return (req, res, next) => {
151
- const fileName = (0, _path.basename)(req.path || req.url?.split("?")[0]);
119
+ const fileName = (0, _utils.getFileNameFromReq)(req);
152
120
  try {
153
121
  const resource = resourcesMap.get(fileName);
154
122
  if (resource) {
155
- res.setHeader("Content-Type", resource.contentType);
156
- res.setHeader("Content-Length", resource.size);
157
- res.setHeader("Last-Modified", resource.mtime.toUTCString());
158
- res.setHeader("Cache-Control", options.immutable === false ? cacheControl : cacheControlImmutable);
159
- if (options.setHeaders && typeof options.setHeaders === "function") {
160
- const statObj = {
161
- mtime: resource.mtime,
162
- size: resource.size
163
- };
164
- options.setHeaders(res, resource.filePath, statObj);
165
- }
166
- const ifModifiedSince = req.headers["if-modified-since"];
167
- if (ifModifiedSince) {
168
- const ifModifiedSinceDate = new Date(ifModifiedSince);
169
- if (resource.mtime <= ifModifiedSinceDate) {
170
- res.statusCode = 304;
171
- res.end();
172
- return;
173
- }
174
- }
175
- const fileStream = (0, _fs.createReadStream)(resource.filePath);
176
- fileStream.on("error", error => {
177
- _utils.logger.error(`Error streaming file ${resource.filePath}:`, error);
178
- next(error);
123
+ (0, _utils.serveResource)(req, res, next, resource, {
124
+ ...options,
125
+ cacheControl,
126
+ cacheControlImmutable
179
127
  });
180
- fileStream.pipe(res);
181
128
  } else {
182
129
  next();
183
130
  }
@@ -1,3 +1,4 @@
1
1
  export * from './middlewares/local-storage';
2
2
  export * from './middlewares/companion';
3
3
  export * from './middlewares/static-resource';
4
+ export * from './middlewares/dynamic-resource';
@@ -35,4 +35,15 @@ Object.keys(_staticResource).forEach(function (key) {
35
35
  return _staticResource[key];
36
36
  }
37
37
  });
38
+ });
39
+ var _dynamicResource = require("./middlewares/dynamic-resource");
40
+ Object.keys(_dynamicResource).forEach(function (key) {
41
+ if (key === "default" || key === "__esModule") return;
42
+ if (key in exports && exports[key] === _dynamicResource[key]) return;
43
+ Object.defineProperty(exports, key, {
44
+ enumerable: true,
45
+ get: function () {
46
+ return _dynamicResource[key];
47
+ }
48
+ });
38
49
  });
package/lib/utils.d.ts CHANGED
@@ -25,4 +25,28 @@ export declare function uploadToMediaKit({ filePath, fileName, base64, extraComp
25
25
  extraComponentCallOptions?: CallComponentOptions;
26
26
  }): Promise<import("axios").AxiosResponse<import("http").IncomingMessage, any> | undefined>;
27
27
  export declare function getMediaKitFileStream(filePath: string): Promise<import("axios").AxiosResponse<import("http").IncomingMessage, any>>;
28
+ export type ResourceFile = {
29
+ filePath: string;
30
+ dir: string;
31
+ originDir: string;
32
+ blockletInfo: any;
33
+ whitelist?: string[];
34
+ blacklist?: string[];
35
+ mtime: Date;
36
+ size: number;
37
+ contentType: string;
38
+ };
39
+ export declare function calculateCacheControl(maxAge?: string | number, immutable?: boolean): {
40
+ cacheControl: string;
41
+ cacheControlImmutable: string;
42
+ maxAgeInSeconds: number;
43
+ };
44
+ export declare function serveResource(req: any, res: any, next: Function, resource: ResourceFile, options?: any): void;
45
+ export declare function scanDirectory(directory: string, options?: {
46
+ whitelist?: string[];
47
+ blacklist?: string[];
48
+ originDir?: string;
49
+ blockletInfo?: any;
50
+ }): Map<string, ResourceFile>;
51
+ export declare function getFileNameFromReq(req: any): any;
28
52
  export {};
package/lib/utils.js CHANGED
@@ -3,16 +3,20 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.calculateCacheControl = calculateCacheControl;
6
7
  exports.checkTrustedReferer = checkTrustedReferer;
7
8
  exports.getFileHash = void 0;
9
+ exports.getFileNameFromReq = getFileNameFromReq;
8
10
  exports.getMediaKitFileStream = getMediaKitFileStream;
9
11
  exports.getTrustedDomainsCache = getTrustedDomainsCache;
10
12
  exports.logger = void 0;
11
13
  exports.proxyImageDownload = proxyImageDownload;
14
+ exports.scanDirectory = scanDirectory;
15
+ exports.serveResource = serveResource;
12
16
  exports.setPDFDownloadHeader = setPDFDownloadHeader;
13
17
  exports.uploadToMediaKit = uploadToMediaKit;
14
18
  var _axios = _interopRequireDefault(require("axios"));
15
- var _path = _interopRequireDefault(require("path"));
19
+ var _path = _interopRequireWildcard(require("path"));
16
20
  var _urlJoin = _interopRequireDefault(require("url-join"));
17
21
  var _isbot = require("isbot");
18
22
  var _component = _interopRequireDefault(require("@blocklet/sdk/lib/component"));
@@ -22,6 +26,10 @@ var _crypto = _interopRequireDefault(require("crypto"));
22
26
  var _verifySign = require("@blocklet/sdk/lib/util/verify-sign");
23
27
  var _formData = _interopRequireDefault(require("form-data"));
24
28
  var _omit = _interopRequireDefault(require("lodash/omit"));
29
+ var _ms = _interopRequireDefault(require("ms"));
30
+ var _mimeTypes = _interopRequireDefault(require("mime-types"));
31
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
32
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
25
33
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
26
34
  let logger = exports.logger = console;
27
35
  if (process.env.BLOCKLET_LOG_DIR) {
@@ -191,4 +199,105 @@ async function getMediaKitFileStream(filePath) {
191
199
  method: "GET"
192
200
  });
193
201
  return res;
202
+ }
203
+ function calculateCacheControl(maxAge = "365d", immutable = true) {
204
+ let maxAgeInSeconds = 31536e3;
205
+ if (typeof maxAge === "string") {
206
+ try {
207
+ const milliseconds = (0, _ms.default)(maxAge);
208
+ maxAgeInSeconds = typeof milliseconds === "number" ? milliseconds / 1e3 : 31536e3;
209
+ } catch (e) {
210
+ logger.warn(`Invalid maxAge format: ${maxAge}, using default 1 year (31536000 seconds)`);
211
+ }
212
+ } else {
213
+ maxAgeInSeconds = maxAge;
214
+ }
215
+ const cacheControl = `public, max-age=${maxAgeInSeconds}`;
216
+ const cacheControlImmutable = `${cacheControl}, immutable`;
217
+ return {
218
+ cacheControl,
219
+ cacheControlImmutable,
220
+ maxAgeInSeconds
221
+ };
222
+ }
223
+ function serveResource(req, res, next, resource, options = {}) {
224
+ try {
225
+ res.setHeader("Content-Type", resource.contentType);
226
+ res.setHeader("Content-Length", resource.size);
227
+ res.setHeader("Last-Modified", resource.mtime.toUTCString());
228
+ const {
229
+ cacheControl,
230
+ cacheControlImmutable
231
+ } = calculateCacheControl(options.maxAge || "365d", options.immutable !== false);
232
+ res.setHeader("Cache-Control", options.immutable === false ? cacheControl : cacheControlImmutable);
233
+ if (options.setHeaders && typeof options.setHeaders === "function") {
234
+ const statObj = {
235
+ mtime: resource.mtime,
236
+ size: resource.size
237
+ };
238
+ options.setHeaders(res, resource.filePath, statObj);
239
+ }
240
+ const ifModifiedSince = req.headers["if-modified-since"];
241
+ if (ifModifiedSince) {
242
+ const ifModifiedSinceDate = new Date(ifModifiedSince);
243
+ if (resource.mtime <= ifModifiedSinceDate) {
244
+ res.statusCode = 304;
245
+ res.end();
246
+ return;
247
+ }
248
+ }
249
+ const fileStream = (0, _fs.createReadStream)(resource.filePath);
250
+ fileStream.on("error", error => {
251
+ logger.error(`Error streaming file ${resource.filePath}:`, error);
252
+ next(error);
253
+ });
254
+ fileStream.pipe(res);
255
+ } catch (error) {
256
+ logger.error("Error serving static file:", error);
257
+ next(error);
258
+ }
259
+ }
260
+ function scanDirectory(directory, options = {}) {
261
+ const resourceMap = /* @__PURE__ */new Map();
262
+ if (!(0, _fs.existsSync)(directory)) {
263
+ return resourceMap;
264
+ }
265
+ try {
266
+ const files = (0, _fs.readdirSync)(directory);
267
+ for (const file of files) {
268
+ const filePath = (0, _path.join)(directory, file);
269
+ let stat;
270
+ try {
271
+ stat = (0, _fs.statSync)(filePath);
272
+ if (stat.isDirectory()) continue;
273
+ } catch (e) {
274
+ continue;
275
+ }
276
+ if (options.whitelist?.length && !options.whitelist.some(ext => file.endsWith(ext))) {
277
+ continue;
278
+ }
279
+ if (options.blacklist?.length && options.blacklist.some(ext => file.endsWith(ext))) {
280
+ continue;
281
+ }
282
+ const contentType = _mimeTypes.default.lookup(filePath) || "application/octet-stream";
283
+ resourceMap.set(file, {
284
+ filePath,
285
+ dir: directory,
286
+ originDir: options.originDir || directory,
287
+ blockletInfo: options.blockletInfo || {},
288
+ whitelist: options.whitelist,
289
+ blacklist: options.blacklist,
290
+ mtime: stat.mtime,
291
+ size: stat.size,
292
+ contentType
293
+ });
294
+ }
295
+ } catch (err) {
296
+ logger.error(`Error scanning directory ${directory}:`, err);
297
+ }
298
+ return resourceMap;
299
+ }
300
+ function getFileNameFromReq(req) {
301
+ const pathname = req.path || req.url?.split("?")[0];
302
+ return _path.default.basename(decodeURIComponent(pathname || ""));
194
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/uploader-server",
3
- "version": "0.1.91",
3
+ "version": "0.1.92",
4
4
  "description": "blocklet upload server",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -59,6 +59,7 @@
59
59
  "axios": "^1.7.8",
60
60
  "body-parser": "^1.20.3",
61
61
  "express-session": "1.17.3",
62
+ "glob": "^11.0.1",
62
63
  "isbot": "^5.1.17",
63
64
  "mime-types": "^2.1.35",
64
65
  "ms": "^2.1.3",