mockaton 8.11.2 → 8.11.3

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
@@ -3,59 +3,66 @@
3
3
  ![NPM Version](https://img.shields.io/npm/v/mockaton)
4
4
  ![NPM Version](https://img.shields.io/npm/l/mockaton)
5
5
 
6
- Mockaton is an HTTP mock server with the goal of making
7
- your frontend development and testing easier—and a lot more fun.
6
+ HTTP mock server for developing and testing frontends.
8
7
 
9
- With Mockaton you don’t need to write code for wiring your mocks.
10
- Instead, just place your mocks in a directory and it will be scanned
11
- for filenames following a convention similar to the URLs.
8
+ ## Convention over Code
12
9
 
13
- For example, for `/api/user/1234` the mock filename would be:
14
- ```
15
- my-mocks-dir/api/user/[user-id].GET.200.json
16
- ```
10
+ With Mockaton you don’t need to write code for wiring mocks. Instead, it scans a
11
+ given directory for mock filenames following a convention similar to the URLs.
17
12
 
13
+ For example, for <code>/<b>api/user</b>/1234</code> the mock filename would be:
14
+ <pre>
15
+ <code>my-mocks-dir/<b>api/user</b>/[user-id].GET.200.json</code>
16
+ </pre>
18
17
 
19
- ## Dashboard
20
- In the dashboard you can select a mock variant for a particular route, among
21
- other features such as delaying responses, or triggering an autogenerated
22
- `500` (Internal Server Error). Nonetheless, there’s a programmatic API,
23
- which is handy for setting up tests (see **Commander API** below).
18
+ ## Multiple Mock Variants
19
+ You can have different mocks for a particular route by adding
20
+ comments and/or using different status codes.
24
21
 
25
- <picture>
26
- <source media="(prefers-color-scheme: light)" srcset="./pixaton-tests/pic-for-readme.vp830x800.light.gold.png">
27
- <source media="(prefers-color-scheme: dark)" srcset="./pixaton-tests/pic-for-readme.vp830x800.dark.gold.png">
28
- <img alt="Mockaton Dashboard" src="./pixaton-tests/pic-for-readme.vp830x800.light.gold.png">
29
- </picture>
30
22
 
23
+ ### Adding comments in filenames
24
+ Comments are anything within parentheses, including them. They
25
+ are ignored for URL purposes, so you can add as many as you want.
31
26
 
27
+ <pre>
28
+ api/login<b>(locked out user)</b>.POST.423.json
29
+ api/login<b>(invalid login attempt)</b>.POST.401.json
30
+ </pre>
32
31
 
33
- ## Fallback to your Backend
34
- Mockaton can fallback to your real backend on routes you don’t have mocks
35
- for. For that, type your backend address in the **Fallback Backend** field.
32
+ ### Different response status code
33
+ For instance, using a `4xx` or `5xx` status code for triggering error
34
+ responses. Or `2xx` such as `204` (No Content) for testing collections without records.
35
+
36
+ <pre>
37
+ api/videos.GET.<b>204</b>.json
38
+ api/videos.GET.<b>403</b>.json
39
+ api/videos.GET.<b>500</b>.empty
40
+ </pre>
36
41
 
37
- Similarly, if you already have mocks for a route you can check the
38
- ☁️ **Cloud checkbox** and that route will be requested from your backend.
39
42
 
43
+ ## Fallback to your Backend
44
+ Mockaton can fallback to your real backend on routes you don’t have
45
+ mocks for, or on routes that have the ☁️ **Cloud Checkbox** checked.
40
46
 
41
- ## Scrapping Mocks from you Backend
47
+
48
+ ### Scrapping Mocks from your Backend
42
49
  If you check **Save Mocks**, Mockaton will collect the responses that hit your backend.
43
- Those mocks will be saved to your `config.mocksDir` following the filename convention.
50
+ They will be saved on your `config.mocksDir` following the filename convention.
44
51
 
45
52
 
46
- ## Multiple Mock Variants
47
53
 
48
- ### Adding comments in filenames
49
- Want to mock a locked-out user or an invalid login attempt?
50
- Add a comment to the filename in parentheses. For example:
54
+ ## Dashboard
55
+ On the dashboard you can select a mock variant for a particular route, delaying responses,
56
+ and triggering an autogenerated `500` (Internal Server Error), among other features.
51
57
 
52
- <pre>
53
- api/login<b>(locked out user)</b>.POST.423.json
54
- </pre>
58
+ Nonetheless, there’s a programmatic API see
59
+ **Commander&nbsp;API** below, which is handy setting up tests.
55
60
 
56
- ### Different response status code
57
- For instance, you can have mocks with a `4xx` or `5xx` status code for triggering
58
- error responses. Or with a `204` (No Content) for testing empty collections.
61
+ <picture>
62
+ <source media="(prefers-color-scheme: light)" srcset="./pixaton-tests/pic-for-readme.vp830x800.light.gold.png">
63
+ <source media="(prefers-color-scheme: dark)" srcset="./pixaton-tests/pic-for-readme.vp830x800.dark.gold.png">
64
+ <img alt="Mockaton Dashboard" src="./pixaton-tests/pic-for-readme.vp830x800.light.gold.png">
65
+ </picture>
59
66
 
60
67
 
61
68
 
@@ -82,24 +89,23 @@ node --import=tsx my-mockaton.js
82
89
  ```
83
90
 
84
91
 
85
- ## Running the demo app (Vite)
92
+ ## Demo App (Vite)
86
93
 
87
94
  This is a minimal React + Vite + Mockaton app. It’s mainly a list of
88
95
  colors, which contains all of their possible states. For example,
89
96
  permutations for out-of-stock, new-arrival, and discontinued.
90
97
 
91
- Also, if you select the **Admin User** from the Mockaton dashboard,
92
- the color cards will have a Delete button as well.
93
-
94
98
  ```sh
95
99
  git clone https://github.com/ericfortis/mockaton.git
96
100
  cd mockaton/demo-app-vite
97
101
  npm install
102
+
98
103
  npm run mockaton
99
104
  npm run start
105
+ # BTW, that directory has scripts for running Mockaton and Vite
106
+ # with one command in two terminals.
100
107
  ```
101
108
 
102
- By the way, that directory has scripts for opening Mockaton and Vite in one command.
103
109
 
104
110
  The app looks like this:
105
111
 
@@ -116,6 +122,7 @@ The app looks like this:
116
122
  - alerts
117
123
  - notifications
118
124
  - slow to build resources
125
+ - Mocking third-party APIs
119
126
 
120
127
  ### Time Travel
121
128
  If you commit the mocks to your repo, it’s straightforward to bisect bugs and
@@ -556,5 +563,4 @@ await mockaton.reset()
556
563
 
557
564
  <div style="display: flex; align-items: center; gap: 20px">
558
565
  <img src="fixtures-mocks/api/user/avatar.GET.200.png" width="170"/>
559
- <p style="font-size: 18px">“Mockaton”</p>
560
566
  </div>
package/package.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "8.11.2",
5
+ "version": "8.11.3",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
9
9
  "repository": "https://github.com/ericfortis/mockaton",
10
10
  "scripts": {
11
- "test": "node --test src/**.test.js",
11
+ "test": "node --test \"src/**/*.test.js\"",
12
12
  "start": "node dev-mockaton.js",
13
13
  "start:ts": "node --import=tsx dev-mockaton.js",
14
14
  "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
@@ -17,8 +17,5 @@
17
17
  "optionalDependencies": {
18
18
  "pixaton": ">=1.0.2",
19
19
  "puppeteer": ">=24.1.1"
20
- },
21
- "devDependencies": {
22
- "pixaton": "1.0.2"
23
20
  }
24
21
  }
package/src/Dashboard.css CHANGED
@@ -1,6 +1,6 @@
1
1
  :root {
2
2
  --boxShadow1: 0 2px 1px -1px rgba(0, 0, 0, 0.15), 0 1px 1px 0 rgba(0, 0, 0, 0.15), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
3
- --radius: 4px;
3
+ --radius: 28px;
4
4
  --radiusSmall: 2px;
5
5
  }
6
6
 
@@ -17,7 +17,7 @@
17
17
  --colorSecondaryAction: #555;
18
18
  --colorDisabledMockSelector: #444;
19
19
  --colorHover: #dfefff;
20
- --colorLabel: #444;
20
+ --colorLabel: #555;
21
21
  --colorLightRed: #ffe4ee;
22
22
  --colorRed: #da0f00;
23
23
  --colorText: #000;
@@ -89,7 +89,7 @@ select {
89
89
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23888888'><path d='M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z'/></svg>") no-repeat;
90
90
  background-color: var(--colorComboBoxBackground);
91
91
  background-size: 16px;
92
- background-position: 100% center;
92
+ background-position: 98% center;
93
93
 
94
94
  &:enabled {
95
95
  box-shadow: var(--boxShadow1);
@@ -115,12 +115,12 @@ select {
115
115
  border-bottom: 1px solid rgba(127, 127, 127, 0.1);
116
116
  background: var(--colorHeaderBackground);
117
117
  box-shadow: var(--boxShadow1);
118
- gap: 10px;
118
+ gap: 6px;
119
119
 
120
120
  img {
121
121
  width: 130px;
122
122
  align-self: center;
123
- margin-right: 22px;
123
+ margin-right: 28px;
124
124
  }
125
125
 
126
126
  .Field {
@@ -129,6 +129,7 @@ select {
129
129
  span {
130
130
  display: flex;
131
131
  align-items: center;
132
+ margin-left: 6px;
132
133
  color: var(--colorLabel);
133
134
  font-size: 11px;
134
135
  gap: 4px;
@@ -148,9 +149,9 @@ select {
148
149
  select {
149
150
  width: 100%;
150
151
  height: 28px;
151
- padding: 4px 8px;
152
+ padding: 4px 12px;
152
153
  border-right: 3px solid transparent;
153
- margin-top: 4px;
154
+ margin-top: 3px;
154
155
  color: var(--colorText);
155
156
  font-size: 11px;
156
157
  box-shadow: var(--boxShadow1);
@@ -163,7 +164,7 @@ select {
163
164
  }
164
165
 
165
166
  &.GlobalDelayField {
166
- width: 84px;
167
+ width: 88px;
167
168
 
168
169
  input[type=number] {
169
170
  padding-right: 0;
@@ -182,10 +183,6 @@ select {
182
183
  position: relative;
183
184
  width: 210px;
184
185
 
185
- input[type=url] {
186
- padding: 0 6px;
187
- }
188
-
189
186
  .SaveProxiedCheckbox {
190
187
  position: absolute;
191
188
  top: 0;
@@ -301,14 +298,14 @@ select {
301
298
  .MockSelector {
302
299
  width: 260px;
303
300
  height: 30px;
304
- padding-right: 5px;
305
- padding-left: 16px;
301
+ padding-right: 11px;
302
+ padding-left: 17px;
306
303
  border: 0;
307
304
  text-align: right;
308
305
  direction: rtl;
309
306
  text-overflow: ellipsis;
310
307
  font-size: 12px;
311
- background-position: 2px center;
308
+ background-position: 5px center;
312
309
 
313
310
  &.nonDefault {
314
311
  font-weight: bold;
@@ -318,7 +315,7 @@ select {
318
315
  background: var(--color4xxBackground);
319
316
  }
320
317
  &:disabled {
321
- padding-right: 4px;
318
+ padding-right: 10px;
322
319
  appearance: none;
323
320
  background: transparent;
324
321
  cursor: default;
@@ -367,8 +364,8 @@ select {
367
364
  }
368
365
 
369
366
  > svg {
370
- width: 18px;
371
- height: 18px;
367
+ width: 20px;
368
+ height: 20px;
372
369
  stroke-width: 2.5px;
373
370
  border-radius: 50%;
374
371
  background: var(--colorSecondaryButtonBackground);
@@ -376,7 +373,7 @@ select {
376
373
  }
377
374
 
378
375
  .ProxyToggler {
379
- padding: 1px 3px;
376
+ padding: 1px 4px;
380
377
  background: var(--colorSecondaryButtonBackground);
381
378
  border-radius: var(--radiusSmall);
382
379
 
@@ -415,8 +412,8 @@ select {
415
412
  }
416
413
 
417
414
  > svg {
418
- width: 16px;
419
- height: 16px;
415
+ width: 18px;
416
+ height: 18px;
420
417
  stroke-width: 2px;
421
418
  border-radius: var(--radiusSmall);
422
419
  }
@@ -444,7 +441,7 @@ select {
444
441
  }
445
442
 
446
443
  > span {
447
- padding: 4px;
444
+ padding: 5px 4px;
448
445
  font-size: 10px;
449
446
  font-weight: bold;
450
447
  color: var(--colorSecondaryAction);
package/src/Dashboard.js CHANGED
@@ -58,6 +58,7 @@ const CSS = {
58
58
  }
59
59
 
60
60
  const r = createElement
61
+ const s = createSvgElement
61
62
  const mockaton = new Commander(window.location.origin)
62
63
 
63
64
  const PROGRESS_BAR_DELAY = 180
@@ -519,16 +520,15 @@ function onError(error) {
519
520
 
520
521
  function TimerIcon() {
521
522
  return (
522
- r('svg', { viewBox: '0 0 24 24' },
523
- r('path', { d: 'm11 5.6 0.14 7.2 6 3.7' })))
523
+ s('svg', { viewBox: '0 0 24 24' },
524
+ s('path', { d: 'm11 5.6 0.14 7.2 6 3.7' })))
524
525
  }
525
526
 
526
527
  function CloudIcon() {
527
528
  return (
528
- r('svg', { viewBox: '0 0 24 24' },
529
- r('path', { d: 'm6.1 8.9c0.98-2.3 3.3-3.9 6-3.9 3.3-2e-7 6 2.5 6.4 5.7 0.018 0.15 0.024 0.18 0.026 0.23 0.0016 0.037 8.2e-4 0.084 0.098 0.14 0.097 0.054 0.29 0.05 0.48 0.05 2.2 0 4 1.8 4 4s-1.8 4-4 4c-4-0.038-9-0.038-13-0.018-2.8 0-5-2.2-5-5-2.2e-7 -2.8 2.2-5 5-5 2.8 2e-7 5 2.2 5 5' }),
530
- r('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })
531
- ))
529
+ s('svg', { viewBox: '0 0 24 24' },
530
+ s('path', { d: 'm6.1 8.9c0.98-2.3 3.3-3.9 6-3.9 3.3-2e-7 6 2.5 6.4 5.7 0.018 0.15 0.024 0.18 0.026 0.23 0.0016 0.037 8.2e-4 0.084 0.098 0.14 0.097 0.054 0.29 0.05 0.48 0.05 2.2 0 4 1.8 4 4s-1.8 4-4 4c-4-0.038-9-0.038-13-0.018-2.8 0-5-2.2-5-5-2.2e-7 -2.8 2.2-5 5-5 2.8 2e-7 5 2.2 5 5' }),
531
+ s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
532
532
  }
533
533
 
534
534
 
@@ -568,16 +568,12 @@ function cssClass(...args) {
568
568
  }
569
569
 
570
570
 
571
- // These are simplified React-compatible implementations.
572
- // IOW, for switching to React, remove the `createRoot`, `createElement`, `useRef`
571
+ // These are simplified React-compatible implementations
573
572
 
574
573
  function createElement(elem, props = null, ...children) {
575
574
  if (typeof elem === 'function')
576
575
  return elem(props)
577
576
 
578
- if (['svg', 'path'].includes(elem)) // Incomplete list
579
- return createSvgElement(elem, props, children)
580
-
581
577
  const node = document.createElement(elem)
582
578
  if (props)
583
579
  for (const [key, value] of Object.entries(props))
package/src/MockBroker.js CHANGED
@@ -3,12 +3,10 @@ import { DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
3
3
 
4
4
 
5
5
  // MockBroker is a state for a particular route. It knows the available mock files
6
- // that can be served for the route, the currently selected file, and its delay.
6
+ // that can be served for the route, the currently selected file, and if it’s delayed.
7
7
  export class MockBroker {
8
- #urlRegex
9
8
  constructor(file) {
10
- const { urlMask } = parseFilename(file)
11
- this.#urlRegex = new RegExp('^' + disregardVariables(removeQueryStringAndFragment(urlMask)) + '/*$')
9
+ this.urlMaskMatches = new UrlMatcher(file).urlMaskMatches
12
10
  this.mocks = []
13
11
  this.currentMock = {
14
12
  file: '',
@@ -17,20 +15,44 @@ export class MockBroker {
17
15
  this.register(file)
18
16
  }
19
17
 
18
+ get file() { return this.currentMock.file }
19
+ get status() { return parseFilename(this.file).status }
20
+ get delayed() { return this.currentMock.delayed }
21
+ get proxied() { return !this.currentMock.file }
22
+ get temp500IsSelected() { return this.#isTemp500(this.file) }
23
+
24
+ hasMock(file) { return this.mocks.includes(file) }
25
+
20
26
  register(file) {
21
27
  if (parseFilename(file).status === 500) {
22
- this.#deleteTemp500()
23
28
  if (this.temp500IsSelected)
24
29
  this.updateFile(file)
30
+ this.#deleteTemp500()
25
31
  }
26
32
  this.mocks.push(file)
27
33
  this.#sortMocks()
28
34
  }
29
35
 
30
- #deleteTemp500() {
31
- this.mocks = this.mocks.filter(file => !this.#isTemp500(file))
36
+ #deleteTemp500() { this.mocks = this.mocks.filter(file => !this.#isTemp500(file)) }
37
+
38
+ #isTemp500(file) { return includesComment(file, DEFAULT_500_COMMENT) }
39
+
40
+ #sortMocks() {
41
+ this.mocks.sort()
42
+ const defaults = this.mocks.filter(file => includesComment(file, DEFAULT_MOCK_COMMENT))
43
+ const temp500 = this.mocks.filter(file => includesComment(file, DEFAULT_500_COMMENT))
44
+ this.mocks = [
45
+ ...defaults,
46
+ ...this.mocks.filter(file => !defaults.includes(file) && !temp500.includes(file)),
47
+ ...temp500
48
+ ]
32
49
  }
33
50
 
51
+ ensureItHas500() {
52
+ if (!this.#has500())
53
+ this.#registerTemp500()
54
+ }
55
+ #has500() { return this.mocks.some(mock => parseFilename(mock).status === 500) }
34
56
  #registerTemp500() {
35
57
  const { urlMask, method } = parseFilename(this.mocks[0])
36
58
  const file = urlMask.replace(/^\//, '') // Removes leading slash
@@ -46,44 +68,18 @@ export class MockBroker {
46
68
  return isEmpty
47
69
  }
48
70
 
49
- // Appending a '/' so URLs ending with variables don't match
50
- // URLs that have a path after that variable. For example,
51
- // without it, the following regex would match both of these URLs:
52
- // api/foo/[route_id] => api/foo/.* (wrong match because it’s too greedy)
53
- // api/foo/[route_id]/suffix => api/foo/.*/suffix
54
- // By the same token, the regex handles many trailing
55
- // slashes. For instance, for routing api/foo/[id]?qs…
56
- urlMaskMatches(url) {
57
- return this.#urlRegex.test(removeQueryStringAndFragment(decodeURIComponent(url)) + '/')
71
+ selectDefaultFile() {
72
+ this.updateFile(this.mocks[0])
58
73
  }
59
74
 
60
- get file() { return this.currentMock.file }
61
- get delayed() { return this.currentMock.delayed }
62
- get proxied() { return !this.currentMock.file }
63
- get status() { return parseFilename(this.file).status }
64
- get temp500IsSelected() { return this.#isTemp500(this.file) }
65
-
66
- #isTemp500(file) { return includesComment(file, DEFAULT_500_COMMENT) }
67
-
68
- #sortMocks() {
69
- this.mocks.sort()
70
- const defaults = this.mocks.filter(file => includesComment(file, DEFAULT_MOCK_COMMENT))
71
- const temp500 = this.mocks.filter(file => includesComment(file, DEFAULT_500_COMMENT))
72
- this.mocks = [
73
- ...defaults,
74
- ...this.mocks.filter(file => !defaults.includes(file) && !temp500.includes(file)),
75
- ...temp500
76
- ]
75
+ updateFile(filename) {
76
+ this.currentMock.file = filename
77
77
  }
78
78
 
79
- selectDefaultFile() {
80
- this.updateFile(this.mocks[0])
79
+ updateDelayed(delayed) {
80
+ this.currentMock.delayed = delayed
81
81
  }
82
82
 
83
- hasMock(file) { return this.mocks.includes(file) }
84
- updateFile(filename) { this.currentMock.file = filename }
85
- updateDelayed(delayed) { this.currentMock.delayed = delayed }
86
-
87
83
  updateProxied(proxied) {
88
84
  if (proxied)
89
85
  this.updateFile('')
@@ -105,22 +101,43 @@ export class MockBroker {
105
101
  comments.push(...extractComments(file))
106
102
  return comments
107
103
  }
104
+ }
108
105
 
109
- ensureItHas500() {
110
- if (!this.#has500())
111
- this.#registerTemp500()
106
+
107
+
108
+ class UrlMatcher {
109
+ #urlRegex
110
+ constructor(file) {
111
+ this.#urlRegex = this.#buildUrlRegex(file)
112
112
  }
113
- #has500() {
114
- return this.mocks.some(mock => parseFilename(mock).status === 500)
113
+
114
+ #buildUrlRegex(file) {
115
+ let { urlMask } = parseFilename(file)
116
+ urlMask = this.#removeQueryStringAndFragment(urlMask)
117
+ urlMask = this.#disregardVariables(urlMask)
118
+ return new RegExp('^' + urlMask + '/*$')
115
119
  }
116
- }
117
120
 
118
- // Stars out (for regex) all the paths that are in square brackets
119
- function disregardVariables(urlMask) {
120
- return urlMask.replace(/\[.*?]/g, '[^/]*')
121
- }
121
+ #removeQueryStringAndFragment(str) {
122
+ return str.replace(/[?#].*/, '')
123
+ }
122
124
 
123
- function removeQueryStringAndFragment(urlMask) {
124
- return urlMask.replace(/[?#].*/, '')
125
+ #disregardVariables(str) { // Stars out all parts that are in square brackets
126
+ return str.replace(/\[.*?]/g, '[^/]*')
127
+ }
128
+
129
+ // Appending a '/' so URLs ending with variables don't match
130
+ // URLs that have a path after that variable. For example,
131
+ // without it, the following regex would match both of these URLs:
132
+ // api/foo/[route_id] => api/foo/.* (wrong match because it’s too greedy)
133
+ // api/foo/[route_id]/suffix => api/foo/.*/suffix
134
+ // By the same token, the regex handles many trailing
135
+ // slashes. For instance, for routing api/foo/[id]?qs…
136
+ urlMaskMatches = (url) => {
137
+ let u = decodeURIComponent(url)
138
+ u = this.#removeQueryStringAndFragment(u)
139
+ u += '/'
140
+ return this.#urlRegex.test(u)
141
+ }
125
142
  }
126
143
 
@@ -34,7 +34,7 @@ export async function dispatchMock(req, response) {
34
34
  : await applyPlugins(join(config.mocksDir, broker.file), req, response)
35
35
 
36
36
  response.setHeader('Content-Type', mime)
37
- setTimeout(() => response.end(body), Number(broker.delayed) * config.delay)
37
+ setTimeout(() => response.end(body), config.delay * broker.delayed)
38
38
  }
39
39
  catch (error) {
40
40
  if (error instanceof BodyReaderError)
package/src/Mockaton.js CHANGED
@@ -38,14 +38,7 @@ async function onRequest(req, response) {
38
38
  response.setHeader('Server', 'Mockaton')
39
39
 
40
40
  if (config.corsAllowed)
41
- setCorsHeaders(req, response, {
42
- origins: config.corsOrigins,
43
- headers: config.corsHeaders,
44
- methods: config.corsMethods,
45
- maxAge: config.corsMaxAge,
46
- credentials: config.corsCredentials,
47
- exposedHeaders: config.corsExposedHeaders
48
- })
41
+ setCorsHeaders(req, response, config)
49
42
 
50
43
  const { url, method } = req
51
44
 
@@ -40,26 +40,26 @@ export function isPreflight(req) {
40
40
 
41
41
 
42
42
  export function setCorsHeaders(req, response, {
43
- origins = [],
44
- methods = [],
45
- headers = [],
46
- exposedHeaders = [],
47
- credentials = false,
48
- maxAge = 0
43
+ corsOrigins = [],
44
+ corsMethods = [],
45
+ corsHeaders = [],
46
+ corsExposedHeaders = [],
47
+ corsCredentials = false,
48
+ corsMaxAge = 0
49
49
  }) {
50
50
  const reqOrigin = req.headers[CH.Origin]
51
- const hasWildcard = origins.some(ao => ao === '*')
52
- if (!reqOrigin || (!hasWildcard && !origins.includes(reqOrigin)))
51
+ const hasWildcard = corsOrigins.some(ao => ao === '*')
52
+ if (!reqOrigin || (!hasWildcard && !corsOrigins.includes(reqOrigin)))
53
53
  return
54
54
  response.setHeader(CH.AccessControlAllowOrigin, reqOrigin) // Never '*', so no need to `Vary` it
55
55
 
56
- if (credentials)
56
+ if (corsCredentials)
57
57
  response.setHeader(CH.AccessControlAllowCredentials, 'true')
58
58
 
59
59
  if (req.headers[CH.AccessControlRequestMethod])
60
- setPreflightSpecificHeaders(req, response, methods, headers, maxAge)
61
- else if (exposedHeaders.length)
62
- response.setHeader(CH.AccessControlExposeHeaders, exposedHeaders.join(','))
60
+ setPreflightSpecificHeaders(req, response, corsMethods, corsHeaders, corsMaxAge)
61
+ else if (corsExposedHeaders.length)
62
+ response.setHeader(CH.AccessControlExposeHeaders, corsExposedHeaders.join(','))
63
63
  }
64
64
 
65
65
  function setPreflightSpecificHeaders(req, response, methods, headers, maxAge) {
@@ -14,10 +14,10 @@ const AllowedDotCom = 'http://allowed.com'
14
14
  const NotAllowedDotCom = 'http://not-allowed.com'
15
15
 
16
16
  await describe('CORS', async () => {
17
- let corsAllow = {}
17
+ let corsConfig = {}
18
18
 
19
19
  const server = createServer((req, response) => {
20
- setCorsHeaders(req, response, corsAllow)
20
+ setCorsHeaders(req, response, corsConfig)
21
21
  if (isPreflight(req)) {
22
22
  response.statusCode = 204
23
23
  response.end()
@@ -82,9 +82,9 @@ await describe('CORS', async () => {
82
82
 
83
83
  await describe('Preflight Response Headers', async () => {
84
84
  await it('no origins allowed', async () => {
85
- corsAllow = {
86
- origins: [],
87
- methods: ['GET']
85
+ corsConfig = {
86
+ corsOrigins: [],
87
+ corsMethods: ['GET']
88
88
  }
89
89
  const p = await preflight({
90
90
  [CH.Origin]: FooDotCom,
@@ -98,9 +98,9 @@ await describe('CORS', async () => {
98
98
  })
99
99
 
100
100
  await it('not in allowed origins', async () => {
101
- corsAllow = {
102
- origins: [AllowedDotCom],
103
- methods: ['GET']
101
+ corsConfig = {
102
+ corsOrigins: [AllowedDotCom],
103
+ corsMethods: ['GET']
104
104
  }
105
105
  const p = await preflight({
106
106
  [CH.Origin]: NotAllowedDotCom,
@@ -113,9 +113,9 @@ await describe('CORS', async () => {
113
113
  })
114
114
 
115
115
  await it('origin and method match', async () => {
116
- corsAllow = {
117
- origins: [AllowedDotCom],
118
- methods: ['GET']
116
+ corsConfig = {
117
+ corsOrigins: [AllowedDotCom],
118
+ corsMethods: ['GET']
119
119
  }
120
120
  const p = await preflight({
121
121
  [CH.Origin]: AllowedDotCom,
@@ -128,9 +128,9 @@ await describe('CORS', async () => {
128
128
  })
129
129
 
130
130
  await it('origin matches from multiple', async () => {
131
- corsAllow = {
132
- origins: [AllowedDotCom, FooDotCom],
133
- methods: ['GET']
131
+ corsConfig = {
132
+ corsOrigins: [AllowedDotCom, FooDotCom],
133
+ corsMethods: ['GET']
134
134
  }
135
135
  const p = await preflight({
136
136
  [CH.Origin]: AllowedDotCom,
@@ -143,9 +143,9 @@ await describe('CORS', async () => {
143
143
  })
144
144
 
145
145
  await it('wildcard origin', async () => {
146
- corsAllow = {
147
- origins: ['*'],
148
- methods: ['GET']
146
+ corsConfig = {
147
+ corsOrigins: ['*'],
148
+ corsMethods: ['GET']
149
149
  }
150
150
  const p = await preflight({
151
151
  [CH.Origin]: FooDotCom,
@@ -158,10 +158,10 @@ await describe('CORS', async () => {
158
158
  })
159
159
 
160
160
  await it(`wildcard and credentials`, async () => {
161
- corsAllow = {
162
- origins: ['*'],
163
- methods: ['GET'],
164
- credentials: true
161
+ corsConfig = {
162
+ corsOrigins: ['*'],
163
+ corsMethods: ['GET'],
164
+ corsCredentials: true
165
165
  }
166
166
  const p = await preflight({
167
167
  [CH.Origin]: FooDotCom,
@@ -174,11 +174,11 @@ await describe('CORS', async () => {
174
174
  })
175
175
 
176
176
  await it(`wildcard, credentials, and headers`, async () => {
177
- corsAllow = {
178
- origins: ['*'],
179
- methods: ['GET'],
180
- credentials: true,
181
- headers: ['content-type', 'my-header']
177
+ corsConfig = {
178
+ corsOrigins: ['*'],
179
+ corsMethods: ['GET'],
180
+ corsCredentials: true,
181
+ corsHeaders: ['content-type', 'my-header']
182
182
  }
183
183
  const p = await preflight({
184
184
  [CH.Origin]: FooDotCom,
@@ -193,9 +193,9 @@ await describe('CORS', async () => {
193
193
 
194
194
  await describe('Non-Preflight (Actual Response) Headers', async () => {
195
195
  await it('no origins allowed', async () => {
196
- corsAllow = {
197
- origins: [],
198
- methods: ['GET']
196
+ corsConfig = {
197
+ corsOrigins: [],
198
+ corsMethods: ['GET']
199
199
  }
200
200
  const p = await request({
201
201
  [CH.Origin]: NotAllowedDotCom
@@ -207,11 +207,11 @@ await describe('CORS', async () => {
207
207
  })
208
208
 
209
209
  await it('origin allowed', async () => {
210
- corsAllow = {
211
- origins: [AllowedDotCom],
212
- methods: ['GET'],
213
- credentials: true,
214
- exposedHeaders: ['x-h1', 'x-h2']
210
+ corsConfig = {
211
+ corsOrigins: [AllowedDotCom],
212
+ corsMethods: ['GET'],
213
+ corsCredentials: true,
214
+ corsExposedHeaders: ['x-h1', 'x-h2']
215
215
  }
216
216
  const p = await request({
217
217
  [CH.Origin]: AllowedDotCom