mbkbucket 2.0.0 → 2.1.0

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.
package/index.js CHANGED
@@ -9,11 +9,14 @@ import rateLimit from 'express-rate-limit';
9
9
  import bucketRoutes from "./lib/routes/index.js";
10
10
  import { checkVersion } from "./lib/config/index.js";
11
11
  import { packageJson } from "./lib/config/index.js";
12
+ import { createLogger } from "./lib/debug.js";
12
13
 
13
14
  dotenv.config();
15
+ const debugServer = createLogger('server');
14
16
 
15
17
  const __filename = fileURLToPath(import.meta.url);
16
18
  const __dirname = path.dirname(__filename);
19
+ const isDevMode = process.env.NODE_ENV === "dev";
17
20
 
18
21
  const server = express();
19
22
  server.set('trust proxy', 1);
@@ -89,11 +92,8 @@ server.set("views", [
89
92
  server.use(mbkautheRoutes);
90
93
  server.use(generalLimiter);
91
94
  server.use(bucketRoutes);
92
-
93
- if (process.env.mbkbucket_test === "dev") {
94
-
95
- console.log("[mbkbucket] Dev mode is enabled. Starting server in dev mode.");
96
-
95
+ if (isDevMode) {
96
+ debugServer("Dev mode is enabled. Starting server in dev mode.");
97
97
 
98
98
  // Request timing middleware: logs method, url, status and elapsed ms
99
99
  server.use((req, res, next) => {
@@ -101,7 +101,7 @@ if (process.env.mbkbucket_test === "dev") {
101
101
  res.on('finish', () => {
102
102
  const diff = process.hrtime(start);
103
103
  const ms = diff[0] * 1000 + diff[1] / 1e6;
104
- console.log(`[mbkbucket] [${new Date().toISOString()}] ${req.method} ${req.originalUrl} ${res.statusCode} - ${ms.toFixed(3)} ms`);
104
+ debugServer("[%s] %s %s %s - %s ms", new Date().toISOString(), req.method, req.originalUrl, res.statusCode, ms.toFixed(3));
105
105
  });
106
106
  next();
107
107
  });
@@ -112,7 +112,7 @@ if (process.env.mbkbucket_test === "dev") {
112
112
 
113
113
  // 404 handler
114
114
  server.use((req, res) => {
115
- console.log(`[mbkbucket] Path not found: ${req.method} ${req.url}`);
115
+ debugServer("Path not found: %s %s", req.method, req.url);
116
116
  return res.status(404).render("Error/dError.handlebars", {
117
117
  layout: false,
118
118
  code: 404,
@@ -138,8 +138,8 @@ if (process.env.mbkbucket_test === "dev") {
138
138
  });
139
139
 
140
140
  const port = process.env.PORT || 3004;
141
- server.listen(port, async () => {
142
- console.log(`[mbkbucket] Server running on http://localhost:${port}`);
141
+ server.listen(port, async() => {
142
+ debugServer("Server running on http://localhost:%s", port);
143
143
  });
144
144
 
145
145
  }
@@ -1,8 +1,10 @@
1
1
  import { createRequire } from "module";
2
2
  import dotenv from "dotenv";
3
3
  import { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection } from "./validation.js";
4
+ import { createLogger } from "../debug.js";
4
5
 
5
6
  dotenv.config();
7
+ const debugConfig = createLogger('config');
6
8
 
7
9
  // Load package.json from mbkbucket package (not parent project)
8
10
  const require = createRequire(import.meta.url);
@@ -28,19 +30,31 @@ try {
28
30
  }
29
31
  }
30
32
 
33
+ function compareVersions(a, b) {
34
+ const left = String(a || '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
35
+ const right = String(b || '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
36
+ const length = Math.max(left.length, right.length);
37
+
38
+ for (let i = 0; i < length; i += 1) {
39
+ const diff = (left[i] || 0) - (right[i] || 0);
40
+ if (diff !== 0) return diff;
41
+ }
42
+ return 0;
43
+ }
44
+
31
45
  // Fetch latest version from GitHub
32
46
  async function getLatestVersion() {
33
47
  try {
34
48
  const response = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkbucket/master/package.json');
35
49
  if (!response.ok) {
36
- console.error(`[mbkbucket] GitHub API responded with status ${response.status}`);
50
+ debugConfig('GitHub API responded with status %s', response.status);
37
51
  return "0.0.0";
38
52
  }
39
53
 
40
54
  const latestPackageJson = await response.json();
41
55
  return latestPackageJson.version;
42
56
  } catch (error) {
43
- console.error('[mbkbucket] Error fetching latest version from GitHub:', error);
57
+ debugConfig('Error fetching latest version from GitHub: %s', error.message);
44
58
  return null;
45
59
  }
46
60
  }
@@ -49,13 +63,15 @@ async function getLatestVersion() {
49
63
  async function checkVersion() {
50
64
  try {
51
65
  const latestVersion = await getLatestVersion();
52
- if (latestVersion && latestVersion !== packageJson.version) {
53
- console.warn(`[mbkbucket] Current version (${packageJson.version}) is outdated. Latest version: ${latestVersion}. Consider updating mbkbucket.`);
66
+ if (latestVersion && compareVersions(latestVersion, packageJson.version) > 0) {
67
+ debugConfig('Current version (%s) is outdated. Latest version: %s.', packageJson.version, latestVersion);
68
+ } else if (latestVersion && compareVersions(latestVersion, packageJson.version) < 0) {
69
+ debugConfig('Current version (%s) is newer than published version: %s.', packageJson.version, latestVersion);
54
70
  } else if (latestVersion) {
55
- console.info(`[mbkbucket] Running latest version (${packageJson.version}).`);
71
+ debugConfig('Running latest version (%s).', packageJson.version);
56
72
  }
57
73
  } catch (error) {
58
- console.warn(`[mbkbucket] Failed to check for updates: ${error.message}`);
74
+ debugConfig('Failed to check for updates: %s', error.message);
59
75
  }
60
76
  }
61
77
 
@@ -64,9 +80,9 @@ function validateConfiguration() {
64
80
  try {
65
81
  const parsed = parseAndValidateMbkbucketVar(process.env.mbkbucketVar);
66
82
  if (!process.env.mbkbucketVar) {
67
- console.log(`[mbkbucket] mbkbucketVar not defined, using defaults`);
83
+ debugConfig('mbkbucketVar not defined, using defaults');
68
84
  }
69
- console.log(`[mbkbucket] Configuration loaded`);
85
+ debugConfig('Configuration loaded');
70
86
  return parsed;
71
87
  } catch (error) {
72
88
  throw new Error(`[mbkbucket] Configuration Validation Failed:\n - ${error.message}`);
@@ -86,7 +102,7 @@ function validateBucketConnection() {
86
102
  const bucketConfig = parseAndValidateBucketConnection(process.env.BucketConnection);
87
103
  const bucketNames = Object.keys(bucketConfig || {});
88
104
 
89
- console.log(`[mbkbucket] ✓ BucketConnection validated (${bucketNames.length} bucket(s): ${bucketNames.join(', ')})`);
105
+ debugConfig('BucketConnection validated (%s bucket(s): %s)', bucketNames.length, bucketNames.join(', '));
90
106
  return bucketConfig;
91
107
  } catch (error) {
92
108
  console.error(`[mbkbucket] ❌ BucketConnection validation failed: ${error.message}`);
@@ -100,11 +116,11 @@ function validateBucketConnection() {
100
116
 
101
117
  // Parse and validate mbkbucketVar once
102
118
  const mbkbucketVar = validateConfiguration();
103
- console.log(`[mbkbucket] mbkbucketVar configuration:`, mbkbucketVar);
119
+ debugConfig('mbkbucketVar configuration: %O', mbkbucketVar);
104
120
 
105
121
  // Validate BucketConnection
106
122
  validateBucketConnection();
107
123
 
108
124
  export { packageJson, appVersion, getLatestVersion, checkVersion };
109
125
  export { mbkbucketVar };
110
- export { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection };
126
+ export { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection };
@@ -1,9 +1,12 @@
1
+ const TRUE_VALUES = new Set(['true', 't', '1', 'yes', 'y', 'on']);
2
+ const FALSE_VALUES = new Set(['false', 'f', '0', 'no', 'n', 'off']);
3
+
1
4
  function normalizeBooleanLike(value) {
2
5
  if (typeof value === 'boolean') return value;
3
6
  if (typeof value === 'string') {
4
7
  const normalized = value.trim().toLowerCase();
5
- if (['true', 't', '1', 'yes', 'y', 'on'].includes(normalized)) return true;
6
- if (['false', 'f', '0', 'no', 'n', 'off'].includes(normalized)) return false;
8
+ if (TRUE_VALUES.has(normalized)) return true;
9
+ if (FALSE_VALUES.has(normalized)) return false;
7
10
  }
8
11
  return value;
9
12
  }
package/lib/debug.js ADDED
@@ -0,0 +1,12 @@
1
+ import createDebug from "debug";
2
+ import dotenv from "dotenv";
3
+
4
+ dotenv.config();
5
+
6
+ if (process.env.DEBUG) {
7
+ createDebug.enable(process.env.DEBUG);
8
+ }
9
+
10
+ export function createLogger(namespace) {
11
+ return createDebug(`mbkbucket:${namespace}`);
12
+ }
package/lib/routes/api.js CHANGED
@@ -2,8 +2,10 @@ import express from "express";
2
2
  import { validateSessionAndRole, } from "mbkauthe";
3
3
  import { listfiles, uploadFile, deleteFile, deleteFiles, deleteFolder, downloadFile, getFileMetadata, fileExists, ensureKeyHasAppPrefix, ensurePrefix, getAppName, createMultipartUpload, uploadPart, completeMultipartUpload, abortMultipartUpload, listIncompleteMultipartUploads, cleanupIncompleteMultipartUploads } from "../s3.js";
4
4
  import multer from "multer";
5
+ import { createLogger } from "../debug.js";
5
6
 
6
7
  const router = express.Router();
8
+ const debugApi = createLogger('api');
7
9
 
8
10
  function classifyApiError(error, fallbackMessage = 'Request failed') {
9
11
  const message = String(error?.message || fallbackMessage);
@@ -35,6 +37,12 @@ function sendApiError(res, error, fallbackMessage = 'Request failed') {
35
37
  return res.status(mapped.status).json({ success: false, code: mapped.code, error: mapped.message });
36
38
  }
37
39
 
40
+ function getBaseName(path = '') {
41
+ const value = String(path || '');
42
+ const lastSlashIndex = value.lastIndexOf('/');
43
+ return lastSlashIndex === -1 ? value : value.substring(lastSlashIndex + 1);
44
+ }
45
+
38
46
  // Central bucket guard for all API routes under /mbkbucket
39
47
  router.use((req, res, next) => {
40
48
  if (req.bucketResolveError) {
@@ -46,9 +54,8 @@ router.use((req, res, next) => {
46
54
  next();
47
55
  });
48
56
 
49
- // This is the maximum size we allow per chunk upload. It should be >=
50
- // the client chunk size (5MB) and leave room for any encoding overhead.
51
- // If you need to support larger chunks, increase this accordingly.
57
+ // This is the maximum size we allow per file part. The dashboard uses the same
58
+ // size as its single-upload threshold and multipart chunk size.
52
59
  const MAX_CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB
53
60
 
54
61
  const upload = multer({
@@ -72,6 +79,20 @@ const uploadChunk = (req, res, next) => {
72
79
  });
73
80
  };
74
81
 
82
+ async function cleanupFolderMarker(folderPath, bucketName) {
83
+ if (!folderPath) return;
84
+
85
+ try {
86
+ const metadata = await getFileMetadata(folderPath, bucketName);
87
+ if (metadata.exists && (metadata.ContentLength === 0 || metadata.Metadata?.marker === 'true')) {
88
+ await deleteFile(folderPath, bucketName);
89
+ debugApi('Cleaned up folder marker: %s', folderPath);
90
+ }
91
+ } catch (cleanupErr) {
92
+ debugApi('Failed to clean up folder marker: %s', cleanupErr.message);
93
+ }
94
+ }
95
+
75
96
  // API endpoint for multipart upload cleanup
76
97
  router.post('/api/cleanup-uploads', validateSessionAndRole('SuperAdmin'), async (req, res) => {
77
98
  try {
@@ -84,7 +105,7 @@ router.post('/api/cleanup-uploads', validateSessionAndRole('SuperAdmin'), async
84
105
  return res.status(400).json({ success: false, error: 'olderThanDays must be a positive integer' });
85
106
  }
86
107
 
87
- console.log(`[mbkbucket] Starting cleanup of incomplete uploads older than ${days} days`);
108
+ debugApi('Starting cleanup of incomplete uploads older than %s days', days);
88
109
  const result = await cleanupIncompleteMultipartUploads(days, prefix, bucketName);
89
110
 
90
111
  res.json({
@@ -166,11 +187,12 @@ router.get('/api/files', validateSessionAndRole('SuperAdmin'), async (req, res)
166
187
  bucketName
167
188
  });
168
189
 
169
- files = result.Contents || [];
170
190
  // Filter out folder marker objects (zero-byte files ending with /)
171
- files = files.filter(f => !f.Key.endsWith('/'));
191
+ files = (result.Contents || []).filter(f => !f.Key.endsWith('/'));
172
192
  // S3 returns CommonPrefixes for folders when delimiter is used
173
- folders = (result.CommonPrefixes || []).map(p => p.Prefix).filter(p => p !== effectivePrefix);
193
+ folders = (result.CommonPrefixes || [])
194
+ .filter(p => p.Prefix !== effectivePrefix)
195
+ .map(p => p.Prefix);
174
196
  nextContinuationToken = result.NextContinuationToken;
175
197
 
176
198
  // Note: In optimized mode, totalFiles is unknown without scanning everything.
@@ -181,10 +203,11 @@ router.get('/api/files', validateSessionAndRole('SuperAdmin'), async (req, res)
181
203
  // Fetch all files recursively (handling S3 pagination)
182
204
  do {
183
205
  const result = await listfiles(effectivePrefix, { continuationToken, bucketName });
184
- if (result.Contents) {
206
+ if (result.Contents?.length) {
185
207
  // Filter out folder marker objects (zero-byte files ending with /)
186
- const actualFiles = result.Contents.filter(f => !f.Key.endsWith('/'));
187
- files = files.concat(actualFiles);
208
+ for (const file of result.Contents) {
209
+ if (!file.Key.endsWith('/')) files.push(file);
210
+ }
188
211
  }
189
212
  continuationToken = result.nextToken;
190
213
  } while (continuationToken);
@@ -199,16 +222,14 @@ router.get('/api/files', validateSessionAndRole('SuperAdmin'), async (req, res)
199
222
  legacyFallback = true;
200
223
  }
201
224
  } catch (fallbackErr) {
202
- console.warn('Fallback root listing failed:', fallbackErr.message);
225
+ debugApi('Fallback root listing failed: %s', fallbackErr.message);
203
226
  }
204
227
  }
205
228
 
206
229
  // Apply search filter if provided
207
230
  if (isSearch) {
208
- const searchLower = search.toLowerCase();
209
- files = files.filter(file => {
210
- return file.Key.toLowerCase().includes(searchLower);
211
- });
231
+ const searchLower = search.trim().toLowerCase();
232
+ files = files.filter(file => file.Key.toLowerCase().includes(searchLower));
212
233
  }
213
234
  }
214
235
 
@@ -299,26 +320,9 @@ router.post('/upload', validateSessionAndRole('SuperAdmin'), upload.single('file
299
320
  try {
300
321
  await uploadFile(key, req.file.buffer, req.file.mimetype, uploadOptions);
301
322
 
302
- // Clean up folder marker if it exists in the parent directory
303
- // This happens when a file is uploaded to a folder that was previously empty
323
+ // Clean up folder marker if it exists in the parent directory.
304
324
  const folderPath = key.substring(0, key.lastIndexOf('/') + 1);
305
- if (folderPath) {
306
- const folderMarkerKey = folderPath; // Already ends with /
307
- try {
308
- const markerExists = await fileExists(folderMarkerKey, bucketName);
309
- if (markerExists) {
310
- // Check if this is actually a folder marker (zero-byte file)
311
- const metadata = await getFileMetadata(folderMarkerKey, bucketName);
312
- if (metadata.ContentLength === 0 || metadata.Metadata?.marker === 'true') {
313
- await deleteFile(folderMarkerKey, bucketName);
314
- console.log(`[mbkbucket] Cleaned up folder marker: ${folderMarkerKey}`);
315
- }
316
- }
317
- } catch (cleanupErr) {
318
- // Non-critical error, just log it
319
- console.warn('[mbkbucket] Failed to clean up folder marker:', cleanupErr.message);
320
- }
321
- }
325
+ await cleanupFolderMarker(folderPath, bucketName);
322
326
 
323
327
  res.json({ success: true, message: 'File uploaded successfully', key: key });
324
328
  } catch (uploadError) {
@@ -399,21 +403,7 @@ router.post('/upload-complete', validateSessionAndRole('SuperAdmin'), async (req
399
403
 
400
404
  // Clean up folder marker if it exists in the parent directory
401
405
  const folderPath = result.key.substring(0, result.key.lastIndexOf('/') + 1);
402
- if (folderPath) {
403
- const folderMarkerKey = folderPath;
404
- try {
405
- const markerExists = await fileExists(folderMarkerKey, bucketName);
406
- if (markerExists) {
407
- const metadata = await getFileMetadata(folderMarkerKey, bucketName);
408
- if (metadata.ContentLength === 0 || metadata.Metadata?.marker === 'true') {
409
- await deleteFile(folderMarkerKey, bucketName);
410
- console.log(`[mbkbucket] Cleaned up folder marker: ${folderMarkerKey}`);
411
- }
412
- }
413
- } catch (cleanupErr) {
414
- console.warn('[mbkbucket] Failed to clean up folder marker:', cleanupErr.message);
415
- }
416
- }
406
+ await cleanupFolderMarker(folderPath, bucketName);
417
407
 
418
408
  return res.json({ success: true, message: 'File uploaded successfully', key: result.key });
419
409
  } catch (err) {
@@ -525,7 +515,7 @@ router.get('/download/:key(*)', validateSessionAndRole('SuperAdmin'), async (req
525
515
  const result = await downloadFile(keyToUse, { bucketName });
526
516
  stream = result.Body;
527
517
 
528
- res.setHeader('Content-Disposition', `attachment; filename="${keyToUse.split('/').pop()}"`);
518
+ res.setHeader('Content-Disposition', `attachment; filename="${getBaseName(keyToUse).replace(/"/g, '\\"')}"`);
529
519
  const contentType = result.ContentType || 'application/octet-stream';
530
520
  res.setHeader('Content-Type', contentType);
531
521
 
@@ -592,4 +582,4 @@ router.get('/download/:key(*)', validateSessionAndRole('SuperAdmin'), async (req
592
582
  }
593
583
  });
594
584
 
595
- export default router;
585
+ export default router;
@@ -3,6 +3,9 @@ import { validateSessionAndRole } from "mbkauthe";
3
3
  import { downloadFile, getFileMetadata, ensureKeyHasAppPrefix, generateSignedUrl, resolveBucketName } from "../s3.js";
4
4
  import { mbkbucketVar } from "../config/index.js";
5
5
  import rateLimit from "express-rate-limit";
6
+ import { createLogger } from "../debug.js";
7
+
8
+ const debugView = createLogger('view');
6
9
 
7
10
  // ---------------------------------------------------------------------------
8
11
  // p_view security: rate limiter + basic bot/security middleware
@@ -42,9 +45,6 @@ function pviewSecurity(req, res, next) {
42
45
  return res.status(403).json({ message: 'Forbidden' });
43
46
  }
44
47
 
45
- // Rate limit check - ensure unique fingerprint per request
46
- const fingerprint = `${req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress}:${ua}`;
47
-
48
48
  // Verify Accept header is present (legitimate browsers always send this)
49
49
  if (!req.headers['accept']) {
50
50
  return res.status(403).json({ message: 'Forbidden' });
@@ -63,6 +63,21 @@ function pviewSecurity(req, res, next) {
63
63
 
64
64
  const router = express.Router();
65
65
 
66
+ function escapeHtml(value = '') {
67
+ return String(value)
68
+ .replace(/&/g, '&amp;')
69
+ .replace(/</g, '&lt;')
70
+ .replace(/>/g, '&gt;')
71
+ .replace(/"/g, '&quot;')
72
+ .replace(/'/g, '&#39;');
73
+ }
74
+
75
+ function getBaseName(path = '') {
76
+ const value = String(path || '');
77
+ const lastSlashIndex = value.lastIndexOf('/');
78
+ return lastSlashIndex === -1 ? value : value.substring(lastSlashIndex + 1);
79
+ }
80
+
66
81
  function classifyViewError(error, fallbackMessage = 'Request failed') {
67
82
  const message = String(error?.message || fallbackMessage);
68
83
  const lower = message.toLowerCase();
@@ -109,7 +124,8 @@ router.get('/player/:key(*)', validateSessionAndRole('SuperAdmin'), async (req,
109
124
  return res.status(400).send(e.message);
110
125
  }
111
126
  const encoded = encodeURIComponent(keyToUse);
112
- const fileName = keyToUse.split('/').pop();
127
+ const fileName = getBaseName(keyToUse);
128
+ const safeFileName = escapeHtml(fileName);
113
129
 
114
130
  const bucketQuery = `?bucket=${encodeURIComponent(bucketName)}`;
115
131
  const html = `<!doctype html>
@@ -117,7 +133,7 @@ router.get('/player/:key(*)', validateSessionAndRole('SuperAdmin'), async (req,
117
133
  <head>
118
134
  <meta charset="utf-8">
119
135
  <meta name="viewport" content="width=device-width,initial-scale=1">
120
- <title>Preview: ${fileName}</title>
136
+ <title>Preview: ${safeFileName}</title>
121
137
  <style>
122
138
  * { margin: 0; padding: 0; box-sizing: border-box; }
123
139
  body { margin: 0; display: flex; align-items: center; justify-content: center; min-height: 100vh; background: #000; color: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
@@ -127,7 +143,7 @@ router.get('/player/:key(*)', validateSessionAndRole('SuperAdmin'), async (req,
127
143
  </style>
128
144
  </head>
129
145
  <body>
130
- <div class="file-name">${fileName}</div>
146
+ <div class="file-name">${safeFileName}</div>
131
147
  <div class="player-container">
132
148
  <video controls preload="metadata" src="/mbkbucket/view/${encoded}${bucketQuery}">
133
149
  Your browser does not support the video tag.
@@ -159,6 +175,9 @@ const VIEWABLE_TYPES = new Set([
159
175
  ]);
160
176
  // Pre-sorted for 415 error responses (avoids re-sorting on every error)
161
177
  const VIEWABLE_TYPES_SORTED = Array.from(VIEWABLE_TYPES).sort();
178
+ const STATIC_ASSET_TYPES = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'css', 'js']);
179
+ const VIDEO_TYPES = new Set(['mp4', 'webm', 'ogg', 'avi', 'mov']);
180
+ const AUDIO_TYPES = new Set(['mp3', 'wav', 'flac', 'aac', 'm4a']);
162
181
 
163
182
  const MIME_TYPES = {
164
183
  // Images
@@ -218,10 +237,11 @@ function buildCacheControl(contentType, { isStaticAsset = false, supportsRanges
218
237
  }
219
238
 
220
239
  async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName, publicCache = false } = {}) {
221
- const fileName = keyToUse.split('/').pop();
222
- const fileExtension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
240
+ const fileName = getBaseName(keyToUse);
241
+ const dotIndex = fileName.lastIndexOf('.');
242
+ const fileExtension = dotIndex === -1 ? '' : fileName.substring(dotIndex + 1).toLowerCase();
223
243
 
224
- console.log(`[mbkbucket] 📥 Request for ${fileName} (type: ${fileExtension}, range: ${req.headers.range || 'none'})`);
244
+ debugView('Request for %s (type: %s, range: %s)', fileName, fileExtension, req.headers.range || 'none');
225
245
 
226
246
  if (!VIEWABLE_TYPES.has(fileExtension)) {
227
247
  return res.status(415).json({
@@ -234,9 +254,9 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
234
254
  const ifModifiedSince = req.headers['if-modified-since'];
235
255
  const rangeHeader = req.headers.range;
236
256
 
237
- const isStaticAsset = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'css', 'js'].includes(fileExtension);
238
- const isVideo = ['mp4', 'webm', 'ogg', 'avi', 'mov'].includes(fileExtension);
239
- const isAudio = ['mp3', 'wav', 'flac', 'aac', 'm4a'].includes(fileExtension);
257
+ const isStaticAsset = STATIC_ASSET_TYPES.has(fileExtension);
258
+ const isVideo = VIDEO_TYPES.has(fileExtension);
259
+ const isAudio = AUDIO_TYPES.has(fileExtension);
240
260
  const isPdf = fileExtension === 'pdf';
241
261
  const supportsRanges = isVideo || isAudio || isPdf;
242
262
  const contentType = MIME_TYPES[fileExtension] || 'application/octet-stream';
@@ -247,7 +267,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
247
267
  // requested byte range — avoids downloading the full file first.
248
268
  // ---------------------------------------------------------------------------
249
269
  if (supportsRanges && rangeHeader) {
250
- console.log(`[mbkbucket] Processing range request: ${rangeHeader} for ${fileName}`);
270
+ debugView('Processing range request: %s for %s', rangeHeader, fileName);
251
271
  let meta;
252
272
  try {
253
273
  meta = await getFileMetadata(keyToUse, bucketName);
@@ -309,10 +329,10 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
309
329
  const rangeResult = await downloadFile(keyToUse, { range: `bytes=${start}-${end}`, bucketName });
310
330
 
311
331
  if (typeof rangeResult.ContentLength === 'number' && rangeResult.ContentLength !== chunksize) {
312
- console.warn(`Range size mismatch for ${keyToUse}: expected=${chunksize} got=${rangeResult.ContentLength}`);
332
+ debugView('Range size mismatch for %s: expected=%s got=%s', keyToUse, chunksize, rangeResult.ContentLength);
313
333
  }
314
334
 
315
- console.log(`[mbkbucket] ✓ Serving range bytes ${start}-${end}/${total} (${chunksize} bytes) for ${fileName}`);
335
+ debugView('Serving range bytes %s-%s/%s (%s bytes) for %s', start, end, total, chunksize, fileName);
316
336
 
317
337
  res.status(206);
318
338
  res.setHeader('Content-Range', `bytes ${start}-${end}/${total}`);
@@ -370,7 +390,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
370
390
  };
371
391
 
372
392
  if (isPdf) {
373
- console.log(`[mbkbucket] Serving full PDF (no range header) for ${fileName}`);
393
+ debugView('Serving full PDF (no range header) for %s', fileName);
374
394
  }
375
395
 
376
396
  let result;
@@ -508,4 +528,4 @@ if (mbkbucketVar?.publiView_enabled) {
508
528
  });
509
529
  }
510
530
 
511
- export default router;
531
+ export default router;