mockaton 8.16.4 → 8.16.6

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/README.md CHANGED
@@ -62,14 +62,20 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
62
62
 
63
63
  <br/>
64
64
 
65
- ## Fallback to Your Backend
66
- No need to mock everything. You can forward requests to your backend for routes
67
- you don’t have mocks for, or routes that have the ☁️ **Cloud Checkbox** checked.
65
+ ## Scraping mocks from your Backend
68
66
 
67
+ ### Option 1: Browser Extension
68
+ This [browser extension](https://github.com/ericfortis/download-http-requests-browser-ext)
69
+ lets you download all the HTTP responses following the filename convention.
69
70
 
70
- ### Scraping mocks from your backend
71
- By checking **Save Mocks**, you can collect the responses that hit your backend.
72
- They will be saved in your `config.mocksDir` following the filename convention.
71
+ ### Option 2: Fallback to Your Backend
72
+ This option could be a bit elaborate if your backend uses third-party auth,
73
+ because in that case you’d have to inject the cookies manually.
74
+
75
+ At any rate, you can forward requests to your backend for routes you don’t have
76
+ mocks for, or routes that have the ☁️ **Cloud Checkbox** checked. By checking
77
+ ✅ **Save Mocks**, you can collect the responses that hit your backend. They
78
+ will be saved in your `config.mocksDir` following the filename convention.
73
79
 
74
80
 
75
81
  <br/>
@@ -226,8 +232,17 @@ export default function listColors() {
226
232
  ```
227
233
  </details>
228
234
 
229
- **What if I need to serve a static .js?**
230
- Put it in your `config.staticDir` without the mock filename convention.
235
+ **What if I need to serve a static .js or .ts?**
236
+
237
+ **Option A:** Put it in your `config.staticDir` without the filename extension convention (i.e., no `.GET.200.js`)
238
+
239
+ **Option B:** Read it and return it. For example:
240
+ ```js
241
+ export default function (_, response) {
242
+ response.setHeader('Content-Type', 'application/javascript')
243
+ return readFileSync('./some-dir/foo.js', 'utf8')
244
+ }
245
+ ```
231
246
 
232
247
  <br/>
233
248
 
@@ -331,10 +346,6 @@ api/foo/bar.GET.200.json
331
346
  This is the only required field. The directory must exist.
332
347
 
333
348
  ### `staticDir?: string`
334
- - Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
335
- - Use Case 2: For a standalone demo server. For example,
336
- build your frontend bundle, and serve it from Mockaton.
337
-
338
349
  Files under `config.staticDir` don’t use the filename convention, and
339
350
  they take precedence over corresponding `GET` mocks in `config.mocksDir`.
340
351
  For example, if you have two files for `GET /foo/bar.jpg`
@@ -344,6 +355,14 @@ my-static-dir<b>/foo/bar.jpg</b>
344
355
  my-mocks-dir<b>/foo/bar.jpg</b>.GET.200.jpg // Unreachable
345
356
  </pre>
346
357
 
358
+ This `config.staticDir` is not actually needed besides serving partial content
359
+ (e.g., videos). At any rate, it’s convenient for serving 200 GET requests without
360
+ having to add the filename extension convention (i.e., no `.GET.200.ext`).
361
+ For example, for using Mockaton as a standalone demo server. For that, you
362
+ can build your frontend bundle and put its built assets in this folder.
363
+
364
+ <br/>
365
+
347
366
  ### `ignore?: RegExp`
348
367
  Defaults to `/(\.DS_Store|~)$/`
349
368
 
package/dev-config.js ADDED
@@ -0,0 +1,18 @@
1
+ import { join } from 'node:path'
2
+ import { jwtCookie } from './index.js'
3
+
4
+
5
+ export const devConfig = {
6
+ port: 2345,
7
+ mocksDir: join(import.meta.dirname, 'fixtures-mocks'),
8
+ staticDir: join(import.meta.dirname, 'fixtures-static-mocks'),
9
+ cookies: {
10
+ 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
11
+ 'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
12
+ 'My JWT': jwtCookie('my-cookie', {
13
+ email: 'john.doe@example.com',
14
+ picture: 'https://cdn.auth0.com/avatars/jd.png'
15
+ }),
16
+ 'None': ''
17
+ }
18
+ }
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Mockaton } from './src/Mockaton.js'
2
2
  export { Commander } from './src/Commander.js'
3
- export { jsToJsonPlugin } from './src/MockDispatcherPlugins.js'
4
3
 
5
4
  export { jwtCookie } from './src/utils/jwt.js'
6
5
  export { parseJSON } from './src/utils/http-request.js'
6
+ export { jsToJsonPlugin } from './src/MockDispatcher.js'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "8.16.4",
5
+ "version": "8.16.6",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -7,11 +7,11 @@ import { join } from 'node:path'
7
7
  import { cookie } from './cookie.js'
8
8
  import { parseJSON } from './utils/http-request.js'
9
9
  import { uiSyncVersion } from './Watcher.js'
10
+ import * as staticCollection from './staticCollection.js'
10
11
  import * as mockBrokersCollection from './mockBrokersCollection.js'
11
12
  import { config, ConfigValidator } from './config.js'
12
13
  import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
13
14
  import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/http-response.js'
14
- import { getStaticFilesCollection, findStaticBrokerByRoute, initStaticCollection } from './StaticDispatcher.js'
15
15
 
16
16
 
17
17
  const dashboardAssets = [
@@ -25,9 +25,7 @@ const dashboardAssets = [
25
25
 
26
26
  export const apiGetRequests = new Map([
27
27
  [API.dashboard, serveDashboard],
28
- ...dashboardAssets.map(f => [
29
- API.dashboard + f, serveDashboardAsset(f)
30
- ]),
28
+ ...dashboardAssets.map(f => [API.dashboard + f, serveDashboardAsset(f)]),
31
29
  [API.cors, getIsCorsAllowed],
32
30
  [API.static, listStaticFiles],
33
31
  [API.mocks, listMockBrokers],
@@ -68,12 +66,14 @@ function serveDashboardAsset(f) {
68
66
 
69
67
  function listCookies(_, response) { sendJSON(response, cookie.list()) }
70
68
  function listComments(_, response) { sendJSON(response, mockBrokersCollection.extractAllComments()) }
71
- function listStaticFiles(_, response) { sendJSON(response, getStaticFilesCollection()) }
69
+
70
+ function listStaticFiles(_, response) { sendJSON(response, staticCollection.all()) }
71
+ function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.all()) }
72
+
72
73
  function getGlobalDelay(_, response) { sendJSON(response, config.delay) }
73
- function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.getAll()) }
74
74
  function getProxyFallback(_, response) { sendJSON(response, config.proxyFallback) }
75
- function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
76
75
  function getCollectProxied(_, response) { sendJSON(response, config.collectProxied) }
76
+ function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
77
77
 
78
78
  function longPollClientSyncVersion(req, response) {
79
79
  if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
@@ -101,7 +101,7 @@ function longPollClientSyncVersion(req, response) {
101
101
 
102
102
  function reinitialize(_, response) {
103
103
  mockBrokersCollection.init()
104
- initStaticCollection() // TESTME
104
+ staticCollection.init() // TESTME
105
105
  sendOK(response)
106
106
  }
107
107
 
@@ -115,7 +115,7 @@ async function selectCookie(req, response) {
115
115
 
116
116
  async function selectMock(req, response) {
117
117
  const file = await parseJSON(req)
118
- const broker = mockBrokersCollection.findBrokerByFilename(file)
118
+ const broker = mockBrokersCollection.brokerByFilename(file)
119
119
  if (!broker || !broker.hasMock(file))
120
120
  sendUnprocessableContent(response, `Missing Mock: ${file}`)
121
121
  else {
@@ -127,7 +127,7 @@ async function selectMock(req, response) {
127
127
  async function setRouteIsDelayed(req, response) {
128
128
  const body = await parseJSON(req)
129
129
  const delayed = body[DF.delayed]
130
- const broker = mockBrokersCollection.findBrokerByRoute(
130
+ const broker = mockBrokersCollection.brokerByRoute(
131
131
  body[DF.routeMethod],
132
132
  body[DF.routeUrlMask])
133
133
 
@@ -144,7 +144,7 @@ async function setRouteIsDelayed(req, response) {
144
144
  async function setRouteIsProxied(req, response) { // TESTME
145
145
  const body = await parseJSON(req)
146
146
  const proxied = body[DF.proxied]
147
- const broker = mockBrokersCollection.findBrokerByRoute(
147
+ const broker = mockBrokersCollection.brokerByRoute(
148
148
  body[DF.routeMethod],
149
149
  body[DF.routeUrlMask])
150
150
 
@@ -201,7 +201,7 @@ async function setGlobalDelay(req, response) { // TESTME
201
201
  async function setStaticRouteStatusCode(req, response) {
202
202
  const body = await parseJSON(req)
203
203
  const status = Number(body[DF.statusCode])
204
- const broker = findStaticBrokerByRoute(body[DF.routeUrlMask])
204
+ const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
205
205
 
206
206
  if (!broker) // TESTME
207
207
  sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
@@ -217,7 +217,7 @@ async function setStaticRouteStatusCode(req, response) {
217
217
  async function setStaticRouteIsDelayed(req, response) {
218
218
  const body = await parseJSON(req)
219
219
  const delayed = body[DF.delayed]
220
- const broker = findStaticBrokerByRoute(body[DF.routeUrlMask])
220
+ const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
221
221
 
222
222
  if (!broker) // TESTME
223
223
  sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
@@ -31,7 +31,7 @@ export const DF = { // Dashboard Fields (XHR)
31
31
  export const DEFAULT_500_COMMENT = '(Mockaton 500)'
32
32
  export const DEFAULT_MOCK_COMMENT = '(default)'
33
33
  export const EXT_FOR_UNKNOWN_MIME = 'unknown'
34
-
35
34
  export const LONG_POLL_SERVER_TIMEOUT = 8_000
36
35
 
36
+
37
37
  export const HEADER_FOR_502 = 'mockaton502'
package/src/Dashboard.css CHANGED
@@ -506,10 +506,8 @@ table {
506
506
 
507
507
  .SpinnerClock {
508
508
  display: flex;
509
- width: 36px;
510
- height: 36px;
511
- margin-top: 92px;
512
- justify-self: center;
509
+ width: 48px;
510
+ margin-top: 6px;
513
511
  fill: none;
514
512
  stroke: var(--colorSecondaryAction);
515
513
  stroke-width: 2px;
@@ -539,6 +537,42 @@ table {
539
537
  filter: saturate(0);
540
538
  }
541
539
 
540
+ .ErrorToast {
541
+ position: fixed;
542
+ bottom: 12px;
543
+ left: 12px;
544
+ z-index: 9999;
545
+ cursor: pointer;
546
+ background: var(--colorRed);
547
+ color: white;
548
+ padding: 12px 16px;
549
+ border-radius: var(--radius);
550
+ box-shadow: var(--boxShadow1);
551
+ opacity: 0;
552
+ transform: translateY(20px);
553
+ animation: _kfToastIn 240ms forwards;
554
+
555
+ &:hover {
556
+ &::after {
557
+ top: 0;
558
+ left: calc(100% - 6px);
559
+ position: absolute;
560
+ content: '×';
561
+ vertical-align: middle;
562
+ padding: 12px;
563
+ border-radius: var(--radius);
564
+ background: black;
565
+ }
566
+ }
567
+ }
568
+
569
+ @keyframes _kfToastIn {
570
+ to {
571
+ opacity: 1;
572
+ transform: translateY(0);
573
+ }
574
+ }
575
+
542
576
 
543
577
  /*
544
578
  * Prism
package/src/Dashboard.js CHANGED
@@ -39,6 +39,7 @@ const Strings = {
39
39
  const CSS = {
40
40
  BulkSelector: 'BulkSelector',
41
41
  DelayToggler: 'DelayToggler',
42
+ ErrorToast: 'ErrorToast',
42
43
  FallbackBackend: 'FallbackBackend',
43
44
  Field: 'Field',
44
45
  GlobalDelayField: 'GlobalDelayField',
@@ -592,10 +593,24 @@ function mockSelectorFor(method, urlMask) {
592
593
 
593
594
  function onError(error) {
594
595
  if (error?.message === 'Failed to fetch')
595
- alert('Looks like the Mockaton server is not running')
596
+ showErrorToast('Looks like the Mockaton server is not running')
596
597
  console.error(error)
597
598
  }
598
599
 
600
+ function showErrorToast(msg) {
601
+ document.getElementsByClassName(CSS.ErrorToast)[0]?.remove()
602
+ document.body.appendChild(
603
+ r('div', {
604
+ className: CSS.ErrorToast,
605
+ onClick() {
606
+ const toast = this
607
+ document.startViewTransition(() => {
608
+ toast.remove()
609
+ })
610
+ }
611
+ }, msg))
612
+ }
613
+
599
614
  function TimerIcon() {
600
615
  return (
601
616
  s('svg', { viewBox: '0 0 24 24' },
@@ -1,8 +1,10 @@
1
1
  import { join } from 'node:path'
2
+ import { readFileSync } from 'node:fs'
3
+ import { pathToFileURL } from 'node:url'
2
4
 
3
5
  import { proxy } from './ProxyRelay.js'
4
6
  import { cookie } from './cookie.js'
5
- import { applyPlugins } from './MockDispatcherPlugins.js'
7
+ import { mimeFor } from './utils/mime.js'
6
8
  import { config, calcDelay } from './config.js'
7
9
  import { BodyReaderError } from './utils/http-request.js'
8
10
  import * as mockBrokerCollection from './mockBrokersCollection.js'
@@ -11,7 +13,7 @@ import { sendInternalServerError, sendNotFound, sendUnprocessableContent } from
11
13
 
12
14
  export async function dispatchMock(req, response) {
13
15
  try {
14
- const broker = mockBrokerCollection.findBrokerByRoute(req.method, req.url)
16
+ const broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
15
17
  if (!broker || broker.proxied) {
16
18
  if (config.proxyFallback)
17
19
  await proxy(req, response, Number(broker?.delayed && calcDelay()))
@@ -50,3 +52,26 @@ export async function dispatchMock(req, response) {
50
52
  sendInternalServerError(response, error)
51
53
  }
52
54
  }
55
+
56
+
57
+ async function applyPlugins(filePath, req, response) {
58
+ for (const [regex, plugin] of config.plugins)
59
+ if (regex.test(filePath))
60
+ return await plugin(filePath, req, response)
61
+ return {
62
+ mime: mimeFor(filePath),
63
+ body: readFileSync(filePath)
64
+ }
65
+ }
66
+
67
+
68
+ export async function jsToJsonPlugin(filePath, req, response) {
69
+ const jsExport = (await import(pathToFileURL(filePath) + '?' + Date.now())).default // date for cache busting
70
+ const body = typeof jsExport === 'function'
71
+ ? await jsExport(req, response)
72
+ : JSON.stringify(jsExport, null, 2)
73
+ return {
74
+ mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
75
+ body
76
+ }
77
+ }
package/src/Mockaton.js CHANGED
@@ -3,12 +3,13 @@ import { createServer } from 'node:http'
3
3
  import { API } from './ApiConstants.js'
4
4
  import { config, setup } from './config.js'
5
5
  import { dispatchMock } from './MockDispatcher.js'
6
+ import { dispatchStatic } from './StaticDispatcher.js'
7
+ import * as staticCollection from './staticCollection.js'
6
8
  import { BodyReaderError } from './utils/http-request.js'
7
9
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
10
  import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
11
+ import { watchMocksDir, watchStaticDir } from './Watcher.js'
9
12
  import { apiPatchRequests, apiGetRequests } from './Api.js'
10
- import { watchMocksDir, watchStaticMocksDir } from './Watcher.js'
11
- import { dispatchStatic, initStaticCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
12
13
  import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from './utils/http-response.js'
13
14
 
14
15
 
@@ -17,9 +18,9 @@ process.on('unhandledRejection', error => { throw error })
17
18
  export function Mockaton(options) {
18
19
  setup(options)
19
20
  mockBrokerCollection.init()
20
- initStaticCollection()
21
+ staticCollection.init()
21
22
  watchMocksDir()
22
- watchStaticMocksDir()
23
+ watchStaticDir()
23
24
 
24
25
  return createServer(onRequest).listen(config.port, config.host, function (error) {
25
26
  if (error) {
@@ -54,7 +55,7 @@ async function onRequest(req, response) {
54
55
  else if (method === 'GET' && apiGetRequests.has(url))
55
56
  apiGetRequests.get(url)(req, response)
56
57
 
57
- else if (method === 'GET' && findStaticBrokerByRoute(url))
58
+ else if (method === 'GET' && staticCollection.brokerByRoute(url))
58
59
  await dispatchStatic(req, response)
59
60
 
60
61
  else
@@ -1,65 +1,14 @@
1
+ import { join } from 'node:path'
1
2
  import { readFileSync } from 'node:fs'
2
- import { join, basename } from 'node:path'
3
3
 
4
4
  import { mimeFor } from './utils/mime.js'
5
- import { listFilesRecursively } from './utils/fs.js'
6
- import { config, isFileAllowed, calcDelay } from './config.js'
7
- import { sendPartialContent, sendNotFound } from './utils/http-response.js'
8
-
9
-
10
- class StaticBroker {
11
- constructor(route) {
12
- this.route = route
13
- this.delayed = false
14
- this.status = 200 // 200 or 404
15
- }
16
-
17
- setDelayed(value) { this.delayed = value }
18
- setStatus(value) { this.status = value }
19
- }
20
-
21
- /** @type {{ [route: string]: StaticBroker }} */
22
- let collection = {}
23
-
24
- export function initStaticCollection() {
25
- collection = {}
26
- listFilesRecursively(config.staticDir)
27
- .sort()
28
- .forEach(registerStaticMock)
29
- }
30
-
31
-
32
- /** @returns {boolean} registered */
33
- export function registerStaticMock(relativeFile) {
34
- if (!isFileAllowed(basename(relativeFile)))
35
- return false
36
-
37
- const route = '/' + relativeFile
38
- if (findStaticBrokerByRoute(route))
39
- return false
40
-
41
- collection[route] = new StaticBroker(route)
42
- return true
43
- }
44
-
45
-
46
- export function unregisterStaticMock(relativeFile) {
47
- delete collection['/' + relativeFile]
48
- }
49
-
50
-
51
- /** @returns {StaticBroker | undefined} */
52
- export function findStaticBrokerByRoute(route) {
53
- return collection[route] || collection[join(route, 'index.html')]
54
- }
55
-
56
- export function getStaticFilesCollection() {
57
- return collection
58
- }
5
+ import { brokerByRoute } from './staticCollection.js'
6
+ import { config, calcDelay } from './config.js'
7
+ import { sendNotFound, sendPartialContent } from './utils/http-response.js'
59
8
 
60
9
 
61
10
  export async function dispatchStatic(req, response) {
62
- const broker = findStaticBrokerByRoute(req.url)
11
+ const broker = brokerByRoute(req.url)
63
12
 
64
13
  setTimeout(async () => {
65
14
  if (!broker || broker.status === 404) { // TESTME
@@ -75,4 +24,3 @@ export async function dispatchStatic(req, response) {
75
24
  }
76
25
  }, Number(broker.delayed && calcDelay()))
77
26
  }
78
-
package/src/Watcher.js CHANGED
@@ -5,7 +5,7 @@ import { EventEmitter } from 'node:events'
5
5
  import { config } from './config.js'
6
6
  import { isFile } from './utils/fs.js'
7
7
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
- import { registerStaticMock, unregisterStaticMock } from './StaticDispatcher.js'
8
+ import { registerStaticMock, unregisterStaticMock } from './staticCollection.js'
9
9
 
10
10
 
11
11
  /** # AR = Add or Remove Mock Event */
@@ -40,7 +40,7 @@ export function watchMocksDir() {
40
40
  })
41
41
  }
42
42
 
43
- export function watchStaticMocksDir() {
43
+ export function watchStaticDir() {
44
44
  const dir = config.staticDir
45
45
  if (!dir)
46
46
  return
package/src/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { realpathSync } from 'node:fs'
2
2
  import { isDirectory } from './utils/fs.js'
3
3
  import { openInBrowser } from './utils/openInBrowser.js'
4
- import { jsToJsonPlugin } from './MockDispatcherPlugins.js'
4
+ import { jsToJsonPlugin } from './MockDispatcher.js'
5
5
  import { optional, is, validate } from './utils/validate.js'
6
6
  import { SUPPORTED_METHODS } from './utils/http-request.js'
7
7
  import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/http-cors.js'
@@ -23,6 +23,8 @@ import { parseFilename, filenameIsValid } from './Filename.js'
23
23
  */
24
24
  let collection = {}
25
25
 
26
+ export const all = () => collection
27
+
26
28
  export function init() {
27
29
  collection = {}
28
30
  cookie.init(config.cookies)
@@ -39,7 +41,7 @@ export function init() {
39
41
 
40
42
  /** @returns {boolean} registered */
41
43
  export function registerMock(file, isFromWatcher = false) {
42
- if (findBrokerByFilename(file)?.hasMock(file)
44
+ if (brokerByFilename(file)?.hasMock(file)
43
45
  || !isFileAllowed(basename(file)) // TESTME
44
46
  || !filenameIsValid(file))
45
47
  return false
@@ -62,7 +64,7 @@ export function registerMock(file, isFromWatcher = false) {
62
64
  }
63
65
 
64
66
  export function unregisterMock(file) {
65
- const broker = findBrokerByFilename(file)
67
+ const broker = brokerByFilename(file)
66
68
  if (!broker)
67
69
  return
68
70
  const isEmpty = broker.unregister(file)
@@ -74,11 +76,9 @@ export function unregisterMock(file) {
74
76
  }
75
77
  }
76
78
 
77
- export const getAll = () => collection
78
-
79
79
 
80
80
  /** @returns {MockBroker | undefined} */
81
- export function findBrokerByFilename(file) {
81
+ export function brokerByFilename(file) {
82
82
  const { method, urlMask } = parseFilename(file)
83
83
  if (collection[method])
84
84
  return collection[method][urlMask]
@@ -91,7 +91,7 @@ export function findBrokerByFilename(file) {
91
91
  * BTW, `urlMasks` always start with "/", so there’s no need to
92
92
  * worry about the primacy of array-like keys when iterating.
93
93
  @returns {MockBroker | undefined} */
94
- export function findBrokerByRoute(method, url) {
94
+ export function brokerByRoute(method, url) {
95
95
  if (!collection[method])
96
96
  return
97
97
  const brokers = Object.values(collection[method])
@@ -0,0 +1,56 @@
1
+ import { join, basename } from 'node:path'
2
+ import { listFilesRecursively } from './utils/fs.js'
3
+ import { config, isFileAllowed } from './config.js'
4
+
5
+
6
+ class StaticBroker {
7
+ constructor(route) {
8
+ this.route = route
9
+ this.delayed = false
10
+ this.status = 200 // 200 or 404
11
+ }
12
+
13
+ setDelayed(value) { this.delayed = value }
14
+ setStatus(value) { this.status = value }
15
+ }
16
+
17
+ /** @type {{ [route: string]: StaticBroker }} */
18
+ let collection = {}
19
+
20
+ export const all = () => collection
21
+
22
+ export function init() {
23
+ collection = {}
24
+ listFilesRecursively(config.staticDir)
25
+ .sort()
26
+ .forEach(registerStaticMock)
27
+ }
28
+
29
+
30
+ /** @returns {boolean} registered */
31
+ export function registerStaticMock(relativeFile) {
32
+ if (!isFileAllowed(basename(relativeFile)))
33
+ return false
34
+
35
+ const route = '/' + relativeFile
36
+ if (brokerByRoute(route))
37
+ return false
38
+
39
+ collection[route] = new StaticBroker(route)
40
+ return true
41
+ }
42
+
43
+
44
+ export function unregisterStaticMock(relativeFile) {
45
+ delete collection['/' + relativeFile]
46
+ }
47
+
48
+
49
+ /** @returns {StaticBroker | undefined} */
50
+ export function brokerByRoute(route) {
51
+ return collection[route] || collection[join(route, 'index.html')]
52
+ }
53
+
54
+
55
+
56
+
@@ -1,25 +0,0 @@
1
- import { readFileSync as read } from 'node:fs'
2
- import { mimeFor } from './utils/mime.js'
3
- import { config } from './config.js'
4
-
5
-
6
- export async function applyPlugins(filePath, req, response) {
7
- for (const [regex, plugin] of config.plugins)
8
- if (regex.test(filePath))
9
- return await plugin(filePath, req, response)
10
- return {
11
- mime: mimeFor(filePath),
12
- body: read(filePath)
13
- }
14
- }
15
-
16
- export async function jsToJsonPlugin(filePath, req, response) {
17
- const jsExport = (await import(filePath + '?' + Date.now())).default // date for cache busting
18
- const body = typeof jsExport === 'function'
19
- ? await jsExport(req, response)
20
- : JSON.stringify(jsExport, null, 2)
21
- return {
22
- mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
23
- body
24
- }
25
- }