@cocreate/file-server 1.23.0 → 1.25.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.
Files changed (2) hide show
  1. package/package.json +4 -3
  2. package/src/index.js +95 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/file-server",
3
- "version": "1.23.0",
3
+ "version": "1.25.0",
4
4
  "description": "A simple file-server component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "file-server",
@@ -37,7 +37,8 @@
37
37
  "type": "module",
38
38
  "main": "./src/index.js",
39
39
  "dependencies": {
40
- "@cocreate/server-side-render": "^1.16.0",
41
- "@cocreate/sitemap": "^1.8.0"
40
+ "@cocreate/config": "^1.19.0",
41
+ "@cocreate/server-side-render": "^1.19.0",
42
+ "@cocreate/sitemap": "^1.10.0"
42
43
  }
43
44
  }
package/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /********************************************************************************
2
- * Copyright (C) 2023 CoCreate and Contributors.
2
+ * Copyright (C) 2026 CoCreate and Contributors.
3
3
  *
4
4
  * This program is free software: you can redistribute it and/or modify
5
- * Wu under the terms of the GNU Affero General Public License as published
5
+ * it under the terms of the GNU Affero General Public License as published
6
6
  * by the Free Software Foundation, either version 3 of the License, or
7
7
  * (at your option) any later version.
8
8
  *
@@ -23,27 +23,57 @@
23
23
 
24
24
  import * as render from '@cocreate/server-side-render';
25
25
  import * as sitemap from '@cocreate/sitemap';
26
+ import config from '@cocreate/config';
27
+ import { dotNotationToObject } from '@cocreate/utils';
26
28
 
27
- // Module-level reference injected on-demand during boot to prevent ESM circular loops
29
+ // Module-level reference injected during boot to prevent ESM circular loops
28
30
  let server = null;
29
31
 
32
+ // Module default configuration
33
+ let fileServerConfig = {
34
+ defaultCacheControl: 'public, max-age=3600',
35
+ enableSitemapCheck: true,
36
+ enableThemeDetection: true
37
+ };
38
+
30
39
  /**
31
- * Initializes the File Server module by injecting the parent orchestrator context.
32
- * Bypasses ESM static compile cycles entirely.
33
- * @param {Object} Server - CoCreateServer context instance.
40
+ * Loads file server configuration using @cocreate/config and queries database overrides
41
+ * for 'fileServer' configurations.
42
+ *
43
+ * @returns {Promise<Object>} Resolved configuration object
34
44
  */
35
- export function init(Server) {
36
- server = Server;
37
- return FileServer
45
+ async function loadConfig() {
46
+ const sysConfig = await config.get('fileServer');
47
+
48
+ let dbConfig = null;
49
+ if (server && server.organization_id) {
50
+ dbConfig = await config.query({
51
+ type: 'fileServer',
52
+ organization_id: server.organization_id,
53
+ server: server.host,
54
+ cluster: server.cluster_id
55
+ });
56
+ }
57
+
58
+ if (sysConfig) {
59
+ dotNotationToObject(fileServerConfig, sysConfig);
60
+ }
61
+
62
+ if (dbConfig) {
63
+ dotNotationToObject(fileServerConfig, dbConfig);
64
+ }
65
+
66
+ return fileServerConfig;
38
67
  }
39
68
 
40
69
  /**
41
- * Main stateless entry point for the CoCreate File System request handler.
42
- * Now cleanly self-contained, handling its own urlObject parsing and organization lookups.
70
+ * Main entry point for the CoCreate File System request handler.
71
+ * Handles urlObject parsing, organization lookups, localization, theme detection, and rendering.
72
+ *
43
73
  * @param {Object} req - Native Node HTTP request object
44
74
  * @param {Object} res - Native Node HTTP response object
45
75
  */
46
- export async function send(req, res) {
76
+ async function send(req, res) {
47
77
  try {
48
78
  const activeCrud = server ? server.crud : null;
49
79
 
@@ -110,7 +140,7 @@ export async function send(req, res) {
110
140
  }
111
141
 
112
142
  // 3. Theme Detection
113
- const theme = detectTheme(req, res);
143
+ const theme = fileServerConfig.enableThemeDetection ? detectTheme(req, res) : null;
114
144
 
115
145
  // 4. Resolve exact file resource and status code
116
146
  const { file, src, statusCode, resolvedPathname } = await resolveFile({
@@ -161,7 +191,7 @@ export async function send(req, res) {
161
191
  });
162
192
 
163
193
  // 8. Async Sitemap Check
164
- if (file) {
194
+ if (fileServerConfig.enableSitemapCheck && file) {
165
195
  await sitemap.check({ file, host: hostname, crud: activeCrud });
166
196
  }
167
197
 
@@ -172,15 +202,11 @@ export async function send(req, res) {
172
202
  }
173
203
  }
174
204
 
175
- // ==========================================
176
- // Isolated Helper Functions
177
- // ==========================================
178
-
179
205
  /**
180
206
  * Normalizes slash-ending paths, resolves files via wildcard mappings,
181
207
  * handles 404/403 fallbacks, and fetches asset sources.
182
208
  */
183
- export async function resolveFile({ pathname, hostname, organizationId, crud }) {
209
+ async function resolveFile({ pathname, hostname, organizationId, crud }) {
184
210
  const activeCrud = crud || (server ? server.crud : null);
185
211
  let statusCode = 200;
186
212
  let file;
@@ -269,7 +295,7 @@ export async function resolveFile({ pathname, hostname, organizationId, crud })
269
295
  src = file?.src || "";
270
296
  }
271
297
 
272
- // Bind source markup and calculated status directly onto the file document for downstream consumers (like the SSR engine)
298
+ // Bind source markup and calculated status directly onto the file document for downstream consumers
273
299
  if (file) {
274
300
  file.src = src;
275
301
  file.status = statusCode;
@@ -281,7 +307,7 @@ export async function resolveFile({ pathname, hostname, organizationId, crud })
281
307
  /**
282
308
  * Specifically resolves, tracks, and mirrors system fallbacks from global scopes.
283
309
  */
284
- export async function getDefaultFile({ fileName, hostname, organizationId, crud }) {
310
+ async function getDefaultFile({ fileName, hostname, organizationId, crud }) {
285
311
  const activeCrud = crud || (server ? server.crud : null);
286
312
  const queryData = {
287
313
  method: "object.read",
@@ -373,7 +399,7 @@ export async function getDefaultFile({ fileName, hostname, organizationId, crud
373
399
  /**
374
400
  * Calculates a directory wildcard path pattern representation.
375
401
  */
376
- export function getWildcardPath(pathname) {
402
+ function getWildcardPath(pathname) {
377
403
  const lastIndex = pathname.lastIndexOf("/");
378
404
  const wildcardPath = pathname.substring(0, lastIndex + 1);
379
405
  const wildcardName = pathname.substring(lastIndex + 1);
@@ -390,7 +416,7 @@ export function getWildcardPath(pathname) {
390
416
  /**
391
417
  * Detects color scheme from HTTP headers and updates client indicators.
392
418
  */
393
- export function detectTheme(req, res) {
419
+ function detectTheme(req, res) {
394
420
  let theme;
395
421
  const headerValue = req?.headers && (
396
422
  req.headers["sec-ch-prefers-color-scheme"] ||
@@ -413,7 +439,7 @@ export function detectTheme(req, res) {
413
439
  /**
414
440
  * Standardizes directory matching redirection configurations.
415
441
  */
416
- export function handleTrailingSlash(pathname, urlObject, res) {
442
+ function handleTrailingSlash(pathname, urlObject, res) {
417
443
  if (!pathname.endsWith("/") && !pathname.startsWith("/.well-known/acme-challenge")) {
418
444
  const lastSegment = pathname.split("/").slice(-1)[0];
419
445
  if (!lastSegment.includes(".")) {
@@ -429,7 +455,7 @@ export function handleTrailingSlash(pathname, urlObject, res) {
429
455
  /**
430
456
  * Extracts explicit or implicit preferred localization tokens from endpoints.
431
457
  */
432
- export function parseLanguage(pathname, headers) {
458
+ function parseLanguage(pathname, headers) {
433
459
  const bcp47Regex = /^\/([a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,8})?)\//;
434
460
  const langMatch = pathname.match(bcp47Regex);
435
461
  let lang, langRegion;
@@ -455,10 +481,10 @@ export function parseLanguage(pathname, headers) {
455
481
  /**
456
482
  * Formats, cleans, parses, and executes rendering transformations based on content types.
457
483
  */
458
- export async function normalizeSrc({ src, contentType, pathname, file, organization, urlObject, langRegion, lang, theme, crud }) {
484
+ async function normalizeSrc({ src, contentType, pathname, file, organization, urlObject, langRegion, lang, theme, crud }) {
459
485
  const activeCrud = crud || (server ? server.crud : null);
460
486
  const isBase64Only = (s) =>
461
- typeof s === "string" && /^[A-Za-z0-9+/]+={0,2}$/.test(s) && s.length % 4 === 0;
487
+ typeof s === "string" && s.length > 0 && /^[A-Za-z0-9+/=\-_]+$/.test(s.replace(/\s+/g, '')) && s.replace(/\s+/g, '').length % 4 === 0;
462
488
 
463
489
  const parseDataUri = (s) => {
464
490
  const m = /^data:([^;]+)(;base64)?,(.*)$/is.exec(s);
@@ -469,11 +495,25 @@ export async function normalizeSrc({ src, contentType, pathname, file, organizat
469
495
  // Fonts
470
496
  if (contentType.startsWith("font/") || /\.(woff2?|ttf|otf)$/i.test(pathname)) {
471
497
  if (Buffer.isBuffer(src)) return src;
472
- if (typeof src !== "string") throw new Error("Invalid font src");
473
- const d = parseDataUri(src);
474
- if (d?.isBase64) return Buffer.from(d.data, "base64");
475
- if (isBase64Only(src)) return Buffer.from(src, "base64");
476
- throw new Error("Font data is not valid base64 or data URI");
498
+ if (typeof src !== "string") return Buffer.from("");
499
+
500
+ const cleanedSrc = src.trim();
501
+ const d = parseDataUri(cleanedSrc);
502
+ if (d?.isBase64) {
503
+ return Buffer.from(d.data.replace(/\s+/g, ''), "base64");
504
+ }
505
+
506
+ const stripped = cleanedSrc.replace(/\s+/g, '').replace(/-/g, '+').replace(/_/g, '/');
507
+ if (isBase64Only(stripped)) {
508
+ return Buffer.from(stripped, "base64");
509
+ }
510
+
511
+ // Graceful fallback for binary or raw font strings instead of throwing
512
+ try {
513
+ return Buffer.from(src, "binary");
514
+ } catch (_) {
515
+ return Buffer.from(src);
516
+ }
477
517
  }
478
518
 
479
519
  // Data URIs
@@ -517,7 +557,6 @@ export async function normalizeSrc({ src, contentType, pathname, file, organizat
517
557
 
518
558
  // HTML rendering
519
559
  if (contentType === "text/html") {
520
- // Adapter Closure to independently fetch isolated templates/chunks for SSR
521
560
  const recursiveResolve = async (nestedPathname) => {
522
561
  const resolved = await resolveFile({
523
562
  pathname: nestedPathname,
@@ -552,9 +591,8 @@ export async function normalizeSrc({ src, contentType, pathname, file, organizat
552
591
  /**
553
592
  * Handles setting response headers, caching guidelines, bandwidth tracking, and returning binary strings.
554
593
  */
555
- export async function sendResponse({ res, src, statusCode, headers, file, organizationId, crud, organizationCacheControl }) {
594
+ async function sendResponse({ res, src, statusCode, headers, file, organizationId, crud, organizationCacheControl }) {
556
595
  try {
557
- const activeCrud = crud || (server ? server.crud : null);
558
596
  let cacheControl;
559
597
  if (statusCode >= 400) {
560
598
  cacheControl = "no-cache, no-store, must-revalidate";
@@ -566,7 +604,7 @@ export async function sendResponse({ res, src, statusCode, headers, file, organi
566
604
  cacheControl = val;
567
605
  }
568
606
  } else {
569
- cacheControl = organizationCacheControl || "public, max-age=3600";
607
+ cacheControl = organizationCacheControl || fileServerConfig.defaultCacheControl || "public, max-age=3600";
570
608
  }
571
609
 
572
610
  headers["Cache-Control"] = cacheControl;
@@ -595,17 +633,25 @@ export async function sendResponse({ res, src, statusCode, headers, file, organi
595
633
  console.error("Error finalizing response payload:", error);
596
634
  }
597
635
  }
598
- const FileServer = {
599
- init,
600
- send,
601
- resolveFile,
602
- getDefaultFile,
603
- getWildcardPath,
604
- detectTheme,
605
- handleTrailingSlash,
606
- parseLanguage,
607
- normalizeSrc,
608
- sendResponse
636
+
637
+ /**
638
+ * Initializes the FileServer module.
639
+ *
640
+ * @param {Object} serverInstance - Core server instance
641
+ * @returns {Promise<Object>} FileServer interface object containing methods and config
642
+ */
643
+ export async function init(serverInstance) {
644
+ server = serverInstance;
645
+
646
+ await loadConfig();
647
+
648
+ const fileServer = {
649
+ config: fileServerConfig,
650
+ send,
651
+ resolveFile
652
+ };
653
+
654
+ return fileServer;
609
655
  }
610
- // Default export added to enforce standard import convention across the platform
611
- export default FileServer;
656
+
657
+ export default { init };