mockaton 11.3.0 → 11.4.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/README.md CHANGED
@@ -7,7 +7,8 @@
7
7
  An HTTP mock server for simulating APIs with minimal setup — ideal
8
8
  for testing difficult to reproduce backend states.
9
9
 
10
- ## https://mockaton.com
10
+ ## [mockaton.com ↗](https://mockaton.com)
11
+ ## [Changelog ↗](https://mockaton.com/changelog)
11
12
 
12
13
  ## Overview
13
14
  With Mockaton, you don’t need to write code for wiring up your
@@ -49,9 +50,9 @@ curl localhost:2020/api/user
49
50
 
50
51
 
51
52
  <picture>
52
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp761x740.light.gold.png">
53
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp761x740.dark.gold.png">
54
- <img alt="Mockaton Dashboard" src="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp761x740.dark.gold.png">
53
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp822x740.light.gold.png">
54
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp822x740.dark.gold.png">
55
+ <img alt="Mockaton Dashboard" src="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp822x740.dark.gold.png">
55
56
  </picture>
56
57
 
57
58
  On the dashboard you can:
@@ -64,7 +65,5 @@ Nonetheless, there’s a programmatic API, which is handy for
64
65
  setting up tests (see **Commander&nbsp;API** section below).
65
66
 
66
67
 
67
- ## [Changelog ↗](https://mockaton.com/changelog)
68
-
69
68
  ## License
70
69
  MIT
package/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface Config {
14
14
  staticDir?: string
15
15
  ignore?: RegExp
16
16
  watcherEnabled?: boolean
17
+ watcherDebounceMs?: number
17
18
 
18
19
  host?: string,
19
20
  port?: number
@@ -98,6 +99,7 @@ export interface State {
98
99
  comments: string[]
99
100
 
100
101
  delay: number
102
+ delayJitter: number
101
103
 
102
104
  collectProxied: boolean
103
105
  proxyFallback: string
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "11.3.0",
5
+ "version": "11.4.0",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -23,6 +23,9 @@ export class Commander {
23
23
 
24
24
  /** @returns {Promise<Response>} */
25
25
  setGlobalDelay = delay => this.#patch(API.globalDelay, delay)
26
+
27
+ /** @returns {Promise<Response>} */
28
+ setGlobalDelayJitter = jitterPct => this.#patch(API.globalDelayJitter, jitterPct)
26
29
 
27
30
  /** @returns {Promise<Response>} */
28
31
  setCorsAllowed = value => this.#patch(API.cors, value)
@@ -12,6 +12,7 @@ export const API = {
12
12
  delayStatic: MOUNT + '/delay-static',
13
13
  fallback: MOUNT + '/fallback',
14
14
  globalDelay: MOUNT + '/global-delay',
15
+ globalDelayJitter: MOUNT + '/global-delay-jitter',
15
16
  proxied: MOUNT + '/proxied',
16
17
  reset: MOUNT + '/reset',
17
18
  select: MOUNT + '/select',
@@ -16,6 +16,7 @@ export const store = {
16
16
  cookies: [],
17
17
  comments: [],
18
18
  delay: 0,
19
+ delayJitter: 0,
19
20
 
20
21
  collectProxied: false,
21
22
  proxyFallback: '',
@@ -84,6 +85,15 @@ export const store = {
84
85
  }
85
86
  catch (error) { store.onError(error) }
86
87
  },
88
+
89
+ async setGlobalDelayJitter(value) {
90
+ try {
91
+ const response = await api.setGlobalDelayJitter(value)
92
+ if (!response.ok) throw response
93
+ store.delayJitter = value
94
+ }
95
+ catch (error) { store.onError(error) }
96
+ },
87
97
 
88
98
  async selectCookie(name) {
89
99
  try {
package/src/client/app.js CHANGED
@@ -17,12 +17,13 @@ const CSS = {
17
17
  FallbackBackend: null,
18
18
  Field: null,
19
19
  GlobalDelayField: null,
20
+ GlobalDelayJitterField: null,
21
+ GlobalDelayWrap: null,
20
22
  GroupByMethod: null,
21
23
  InternalServerErrorToggler: null,
22
24
  Logo: null,
23
25
  MenuTrigger: null,
24
26
  Method: null,
25
- MockList: null,
26
27
  MockSelector: null,
27
28
  NotFoundToggler: null,
28
29
  PayloadViewer: null,
@@ -110,7 +111,7 @@ function Header() {
110
111
  r('header', null,
111
112
  r('a', {
112
113
  className: CSS.Logo,
113
- href: 'https://mockaton.com',
114
+ href: 'https://mockaton.com'
114
115
  },
115
116
  r('object', {
116
117
  data: 'logo.svg',
@@ -119,14 +120,18 @@ function Header() {
119
120
  height: 22
120
121
  })),
121
122
  r('div', null,
122
- GlobalDelayField(),
123
+ r('div', className(CSS.GlobalDelayWrap),
124
+ GlobalDelayField(),
125
+ GlobalDelayJitterField()),
123
126
  BulkSelector(),
124
127
  CookieSelector(),
125
128
  ProxyFallbackField(),
126
129
  ResetButton(),
127
- SettingsMenuTrigger())))
130
+ SettingsMenuTrigger()
131
+ )))
128
132
  }
129
133
 
134
+
130
135
  function GlobalDelayField() {
131
136
  function onChange() {
132
137
  store.setGlobalDelay(this.valueAsNumber)
@@ -143,6 +148,7 @@ function GlobalDelayField() {
143
148
  r('label', className(CSS.Field, CSS.GlobalDelayField),
144
149
  r('span', null, t`Delay (ms)`),
145
150
  r('input', {
151
+ name: 'delay',
146
152
  type: 'number',
147
153
  min: 0,
148
154
  step: 100,
@@ -153,6 +159,38 @@ function GlobalDelayField() {
153
159
  })))
154
160
  }
155
161
 
162
+ function GlobalDelayJitterField() {
163
+ function onChange() {
164
+ this.value = this.valueAsNumber.toFixed(0)
165
+ this.value = Math.max(0, this.valueAsNumber)
166
+ this.value = Math.min(300, this.valueAsNumber)
167
+ store.setGlobalDelayJitter(this.valueAsNumber / 100)
168
+ }
169
+ function onWheel(event) {
170
+ if (event.deltaY > 0)
171
+ this.stepUp()
172
+ else
173
+ this.stepDown()
174
+ clearTimeout(onWheel.timer)
175
+ onWheel.timer = setTimeout(onChange.bind(this), 300)
176
+ }
177
+ return (
178
+ r('label', className(CSS.Field, CSS.GlobalDelayJitterField),
179
+ r('span', null, t`Max Jitter %`),
180
+ r('input', {
181
+ name: 'delay-jitter',
182
+ type: 'number',
183
+ min: 0,
184
+ max: 300,
185
+ step: 10,
186
+ autocomplete: 'none',
187
+ value: (store.delayJitter * 100).toFixed(0),
188
+ onChange,
189
+ onWheel: [onWheel, { passive: true }]
190
+ })))
191
+ }
192
+
193
+
156
194
  function BulkSelector() {
157
195
  const { comments } = store
158
196
  const firstOption = t`Pick Comment…`
@@ -209,6 +247,7 @@ function ProxyFallbackField() {
209
247
  r('label', null,
210
248
  r('span', null, t`Fallback`),
211
249
  r('input', {
250
+ name: 'fallback',
212
251
  type: 'url',
213
252
  autocomplete: 'none',
214
253
  placeholder: t`Type backend address`,
@@ -222,6 +261,7 @@ function SaveProxiedCheckbox(ref) {
222
261
  return (
223
262
  r('label', className(CSS.SaveProxiedCheckbox),
224
263
  r('input', {
264
+ name: 'save-proxied',
225
265
  ref,
226
266
  type: 'checkbox',
227
267
  disabled: !store.canProxy,
@@ -269,6 +309,7 @@ function SettingsMenu(id) {
269
309
  r('div', null,
270
310
  r('label', className(CSS.GroupByMethod),
271
311
  r('input', {
312
+ name: 'group-by-method',
272
313
  ref: firstInputRef,
273
314
  type: 'checkbox',
274
315
  checked: store.groupByMethod,
@@ -281,7 +322,7 @@ function SettingsMenu(id) {
281
322
  target: '_blank',
282
323
  rel: 'noopener noreferrer'
283
324
  }, t`Website`),
284
-
325
+
285
326
  r('a', {
286
327
  href: 'https://github.com/ericfortis/mockaton',
287
328
  target: '_blank',
@@ -77,3 +77,4 @@ function selectorFor(elem) {
77
77
  }
78
78
  return path.reverse().join('>')
79
79
  }
80
+
@@ -7,6 +7,7 @@ export const CSP = [
7
7
  ].join(';')
8
8
 
9
9
 
10
+ // language=html
10
11
  export const IndexHtml = hotReloadEnabled => `
11
12
  <!DOCTYPE html>
12
13
  <html lang="en-US">
@@ -137,7 +137,7 @@ header {
137
137
  align-self: end;
138
138
  margin-right: 22px;
139
139
  margin-bottom: 3px;
140
-
140
+
141
141
  object {
142
142
  pointer-events: none;
143
143
  }
@@ -151,10 +151,10 @@ header {
151
151
  gap: 16px 8px;
152
152
  }
153
153
 
154
- @media (max-width: 760px) {
155
- > img {
156
- width: 100px;
157
- align-self: center;
154
+ @media (max-width: 820px) {
155
+ .Logo {
156
+ margin-top: 16px;
157
+ align-self: start;
158
158
  }
159
159
 
160
160
  > div {
@@ -170,6 +170,10 @@ header {
170
170
  }
171
171
  }
172
172
 
173
+ .GlobalDelayWrap {
174
+ display: flex;
175
+ }
176
+
173
177
  .Field {
174
178
  width: 116px;
175
179
 
@@ -202,10 +206,13 @@ header {
202
206
  }
203
207
 
204
208
  &.GlobalDelayField {
209
+ position: relative;
205
210
  width: 76px;
206
211
 
207
- input[type=number] {
212
+ input {
208
213
  padding-right: 6px;
214
+ border-bottom-right-radius: 0;
215
+ border-top-right-radius: 0;
209
216
  }
210
217
 
211
218
  svg {
@@ -215,6 +222,25 @@ header {
215
222
  stroke-width: 3px;
216
223
  border-radius: 50%;
217
224
  }
225
+
226
+ &:focus-within {
227
+ z-index: 100;
228
+ }
229
+ }
230
+
231
+ &.GlobalDelayJitterField {
232
+ width: 70px;
233
+ span {
234
+ margin-left: 0;
235
+ }
236
+ input {
237
+ border-left: 2px solid transparent;
238
+ border-bottom-left-radius: 0;
239
+ border-top-left-radius: 0;
240
+ }
241
+ &:focus-within {
242
+ z-index: 100;
243
+ }
218
244
  }
219
245
 
220
246
  &.CookieSelector {
@@ -223,7 +249,7 @@ header {
223
249
 
224
250
  &.FallbackBackend {
225
251
  position: relative;
226
- width: 160px;
252
+ width: 150px;
227
253
 
228
254
  .SaveProxiedCheckbox {
229
255
  position: absolute;
package/src/server/Api.js CHANGED
@@ -39,6 +39,7 @@ export const apiPatchReqs = new Map([
39
39
  [API.reset, reinitialize],
40
40
  [API.cookies, selectCookie],
41
41
  [API.globalDelay, setGlobalDelay],
42
+ [API.globalDelayJitter, setGlobalDelayJitter],
42
43
 
43
44
  [API.fallback, setProxyFallback],
44
45
  [API.collectProxied, setCollectProxied],
@@ -117,6 +118,17 @@ async function setGlobalDelay(req, response) {
117
118
  }
118
119
  }
119
120
 
121
+ async function setGlobalDelayJitter(req, response) {
122
+ const jitter = await req.json()
123
+
124
+ if (!ConfigValidator.delayJitter(jitter))
125
+ response.unprocessable(`Expected 0 to 3 float for "delayJitter"`)
126
+ else {
127
+ config.delayJitter = jitter
128
+ response.ok()
129
+ }
130
+ }
131
+
120
132
 
121
133
  async function selectCookie(req, response) {
122
134
  const cookieKey = await req.json()
@@ -12,6 +12,7 @@ export const API = {
12
12
  delayStatic: MOUNT + '/delay-static',
13
13
  fallback: MOUNT + '/fallback',
14
14
  globalDelay: MOUNT + '/global-delay',
15
+ globalDelayJitter: MOUNT + '/global-delay-jitter',
15
16
  proxied: MOUNT + '/proxied',
16
17
  reset: MOUNT + '/reset',
17
18
  select: MOUNT + '/select',
@@ -39,7 +39,7 @@ const CUSTOM_EXT = 'custom_extension'
39
39
  const CUSTOM_MIME = 'custom_mime'
40
40
  const CUSTOM_HEADER_NAME = 'custom_header_name'
41
41
  const CUSTOM_HEADER_VAL = 'custom_header_val'
42
- const ALLOWED_ORIGIN = 'http://example.com'
42
+ const ALLOWED_ORIGIN = 'https://example.test'
43
43
 
44
44
  const server = await Mockaton({
45
45
  mocksDir,
@@ -51,7 +51,8 @@ const server = await Mockaton({
51
51
  logLevel: 'verbose',
52
52
  corsOrigins: [ALLOWED_ORIGIN],
53
53
  corsExposedHeaders: ['Content-Encoding'],
54
- watcherEnabled: false,
54
+ watcherEnabled: false, // But we enable it at run-time
55
+ watcherDebounceMs: 0
55
56
  })
56
57
  after(() => server?.close())
57
58
 
@@ -308,7 +309,7 @@ describe('Cookie', () => {
308
309
 
309
310
  describe('Delay', () => {
310
311
  describe('Set Global Delay', () => {
311
- test('422 for invalid global delay value', async () => {
312
+ test('422 for invalid value', async () => {
312
313
  const r = await api.setGlobalDelay('not-a-number')
313
314
  equal(r.status, 422)
314
315
  equal(await r.text(), 'Expected non-negative integer for "delay"')
@@ -320,6 +321,19 @@ describe('Delay', () => {
320
321
  })
321
322
  })
322
323
 
324
+ describe('Set Global Delay Jitter', () => {
325
+ test('422 for invalid value', async () => {
326
+ const r = await api.setGlobalDelayJitter('not-a-number')
327
+ equal(r.status, 422)
328
+ equal(await r.text(), 'Expected 0 to 3 float for "delayJitter"')
329
+ })
330
+ test('200 for valid value', async () => {
331
+ const r = await api.setGlobalDelayJitter(0.1)
332
+ equal(r.status, 200)
333
+ equal((await fetchState()).delayJitter, 0.1)
334
+ })
335
+ })
336
+
323
337
  test('updates route delay', async () => {
324
338
  const fx = new Fixture('route-delay.GET.200.json')
325
339
  await fx.sync()
@@ -404,9 +418,9 @@ describe('Proxy Fallback', () => {
404
418
  })
405
419
 
406
420
  test('sets fallback', async () => {
407
- const r = await api.setProxyFallback('http://example.com')
421
+ const r = await api.setProxyFallback('https://example.test')
408
422
  equal(r.status, 200)
409
- equal((await fetchState()).proxyFallback, 'http://example.com')
423
+ equal((await fetchState()).proxyFallback, 'https://example.test')
410
424
  })
411
425
 
412
426
  test('unsets fallback', async () => {
@@ -461,7 +475,7 @@ describe('Proxy Fallback', () => {
461
475
  })
462
476
 
463
477
  test('200 when setting', async () => {
464
- await api.setProxyFallback('https://example.com')
478
+ await api.setProxyFallback('https://example.test')
465
479
  const r0 = await api.setRouteIsProxied(fx.method, fx.urlMask, true)
466
480
  equal(r0.status, 200)
467
481
  equal((await r0.json()).proxied, true)
@@ -480,7 +494,7 @@ describe('Proxy Fallback', () => {
480
494
  test('unsets auto500', async () => {
481
495
  const fx = new Fixture('unset-500-on-proxy.GET.200.txt')
482
496
  await fx.sync()
483
- await api.setProxyFallback('https://example.com')
497
+ await api.setProxyFallback('https://example.test')
484
498
 
485
499
  const r0 = await api.toggle500(fx.method, fx.urlMask)
486
500
  const b0 = await r0.json()
@@ -500,7 +514,7 @@ describe('Proxy Fallback', () => {
500
514
  test('updating selected mock resets proxied flag', async () => {
501
515
  const fx = new Fixture('select-resets-proxied.GET.200.txt')
502
516
  await fx.sync()
503
- await api.setProxyFallback('http://example.com')
517
+ await api.setProxyFallback('https://example.test')
504
518
  const r0 = await api.setRouteIsProxied(fx.method, fx.urlMask, true)
505
519
  equal((await r0.json()).proxied, true)
506
520
 
@@ -789,7 +803,7 @@ describe('500', () => {
789
803
  test('toggling ON 500 unsets `proxied` flag', async () => {
790
804
  const fx = new Fixture('proxied-to-500.GET.200.txt')
791
805
  await fx.sync()
792
- await api.setProxyFallback('http://example.com')
806
+ await api.setProxyFallback('https://example.test')
793
807
  await api.setRouteIsProxied(fx.method, fx.urlMask, true)
794
808
  await api.toggle500(fx.method, fx.urlMask)
795
809
  equal((await fx.fetchBroker()).proxied, false)
@@ -21,7 +21,6 @@ import * as mockBrokerCollection from './mockBrokersCollection.js'
21
21
  * and also renames, which are two events (delete + add).
22
22
  */
23
23
  const uiSyncVersion = new class extends EventEmitter {
24
- delay = Number(process.env.MOCKATON_WATCHER_DEBOUNCE_MS ?? 80)
25
24
  version = 0
26
25
 
27
26
  increment = /** @type {function} */ this.#debounce(() => {
@@ -40,7 +39,7 @@ const uiSyncVersion = new class extends EventEmitter {
40
39
  let timer
41
40
  return () => {
42
41
  clearTimeout(timer)
43
- timer = setTimeout(fn, this.delay)
42
+ timer = setTimeout(fn, config.watcherDebounceMs)
44
43
  }
45
44
  }
46
45
  }
@@ -1,11 +1,10 @@
1
1
  import { join } from 'node:path'
2
2
  import { EventEmitter } from 'node:events'
3
3
  import { watch, readdirSync } from 'node:fs'
4
+ import { config } from './config.js'
4
5
  import { LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
5
6
 
6
7
 
7
- const DEV = process.env.NODE_ENV === 'development'
8
-
9
8
  export const CLIENT_DIR = join(import.meta.dirname, '../client')
10
9
  export const DASHBOARD_ASSETS = readdirSync(CLIENT_DIR)
11
10
 
@@ -28,7 +27,7 @@ export function watchDevSPA() {
28
27
 
29
28
  /** Realtime notify Dev UI changes */
30
29
  export function longPollDevClientHotReload(req, response) {
31
- if (!DEV) {
30
+ if (!config.hotReload) {
32
31
  response.notFound()
33
32
  return
34
33
  }
@@ -21,6 +21,7 @@ const schema = {
21
21
  staticDir: [resolve('mockaton-static-mocks'), optional(isDirectory)],
22
22
  ignore: [/(\.DS_Store|~)$/, is(RegExp)],
23
23
  watcherEnabled: [true, is(Boolean)],
24
+ watcherDebounceMs: [80, ms => Number.isInteger(ms) && ms >= 0],
24
25
 
25
26
  host: ['127.0.0.1', is(String)],
26
27
  port: [0, port => Number.isInteger(port) && port >= 0 && port < 2 ** 16], // 0 means auto-assigned
@@ -73,9 +74,6 @@ export const ConfigValidator = Object.freeze(validators)
73
74
 
74
75
  /** @param {Partial<Config>} opts */
75
76
  export function setup(opts) {
76
- if (process.env.NODE_ENV !== 'development')
77
- opts.hotReload = false
78
-
79
77
  if (opts.mocksDir)
80
78
  opts.mocksDir = resolve(opts.mocksDir)
81
79
 
@@ -9,9 +9,9 @@ function headerIs(response, header, value) {
9
9
  equal(response.headers.get(header), value)
10
10
  }
11
11
 
12
- const FooDotCom = 'http://foo.com'
13
- const AllowedDotCom = 'http://allowed.com'
14
- const NotAllowedDotCom = 'http://not-allowed.com'
12
+ const FooDotTest = 'https://foo.test'
13
+ const AllowedDotTest = 'https://allowed.test'
14
+ const NotAllowedDotTest = 'https://not-allowed.test'
15
15
 
16
16
  await describe('CORS', async () => {
17
17
  let corsConfig = {}
@@ -87,7 +87,7 @@ await describe('CORS', async () => {
87
87
  corsMethods: ['GET']
88
88
  }
89
89
  const p = await preflight({
90
- [CH.Origin]: FooDotCom,
90
+ [CH.Origin]: FooDotTest,
91
91
  [CH.AcRequestMethod]: 'GET'
92
92
  })
93
93
  headerIs(p, CH.AcAllowOrigin, null)
@@ -99,11 +99,11 @@ await describe('CORS', async () => {
99
99
 
100
100
  await test('not in allowed origins', async () => {
101
101
  corsConfig = {
102
- corsOrigins: [AllowedDotCom],
102
+ corsOrigins: [AllowedDotTest],
103
103
  corsMethods: ['GET']
104
104
  }
105
105
  const p = await preflight({
106
- [CH.Origin]: NotAllowedDotCom,
106
+ [CH.Origin]: NotAllowedDotTest,
107
107
  [CH.AcRequestMethod]: 'GET'
108
108
  })
109
109
  headerIs(p, CH.AcAllowOrigin, null)
@@ -114,14 +114,14 @@ await describe('CORS', async () => {
114
114
 
115
115
  await test('origin and method match', async () => {
116
116
  corsConfig = {
117
- corsOrigins: [AllowedDotCom],
117
+ corsOrigins: [AllowedDotTest],
118
118
  corsMethods: ['GET']
119
119
  }
120
120
  const p = await preflight({
121
- [CH.Origin]: AllowedDotCom,
121
+ [CH.Origin]: AllowedDotTest,
122
122
  [CH.AcRequestMethod]: 'GET'
123
123
  })
124
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
124
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
125
125
  headerIs(p, CH.AcAllowMethods, 'GET')
126
126
  headerIs(p, CH.AcAllowCredentials, null)
127
127
  headerIs(p, CH.AcAllowHeaders, null)
@@ -129,14 +129,14 @@ await describe('CORS', async () => {
129
129
 
130
130
  await test('origin matches from multiple', async () => {
131
131
  corsConfig = {
132
- corsOrigins: [AllowedDotCom, FooDotCom],
132
+ corsOrigins: [AllowedDotTest, FooDotTest],
133
133
  corsMethods: ['GET']
134
134
  }
135
135
  const p = await preflight({
136
- [CH.Origin]: AllowedDotCom,
136
+ [CH.Origin]: AllowedDotTest,
137
137
  [CH.AcRequestMethod]: 'GET'
138
138
  })
139
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
139
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
140
140
  headerIs(p, CH.AcAllowMethods, 'GET')
141
141
  headerIs(p, CH.AcAllowCredentials, null)
142
142
  headerIs(p, CH.AcAllowHeaders, null)
@@ -148,10 +148,10 @@ await describe('CORS', async () => {
148
148
  corsMethods: ['GET']
149
149
  }
150
150
  const p = await preflight({
151
- [CH.Origin]: FooDotCom,
151
+ [CH.Origin]: FooDotTest,
152
152
  [CH.AcRequestMethod]: 'GET'
153
153
  })
154
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
154
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
155
155
  headerIs(p, CH.AcAllowMethods, 'GET')
156
156
  headerIs(p, CH.AcAllowCredentials, null)
157
157
  headerIs(p, CH.AcAllowHeaders, null)
@@ -164,10 +164,10 @@ await describe('CORS', async () => {
164
164
  corsCredentials: true
165
165
  }
166
166
  const p = await preflight({
167
- [CH.Origin]: FooDotCom,
167
+ [CH.Origin]: FooDotTest,
168
168
  [CH.AcRequestMethod]: 'GET'
169
169
  })
170
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
170
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
171
171
  headerIs(p, CH.AcAllowMethods, 'GET')
172
172
  headerIs(p, CH.AcAllowCredentials, 'true')
173
173
  headerIs(p, CH.AcAllowHeaders, null)
@@ -181,10 +181,10 @@ await describe('CORS', async () => {
181
181
  corsHeaders: ['content-type', 'my-header']
182
182
  }
183
183
  const p = await preflight({
184
- [CH.Origin]: FooDotCom,
184
+ [CH.Origin]: FooDotTest,
185
185
  [CH.AcRequestMethod]: 'GET'
186
186
  })
187
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
187
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
188
188
  headerIs(p, CH.AcAllowMethods, 'GET')
189
189
  headerIs(p, CH.AcAllowCredentials, 'true')
190
190
  headerIs(p, CH.AcAllowHeaders, 'content-type,my-header')
@@ -198,7 +198,7 @@ await describe('CORS', async () => {
198
198
  corsMethods: ['GET']
199
199
  }
200
200
  const p = await request({
201
- [CH.Origin]: NotAllowedDotCom
201
+ [CH.Origin]: NotAllowedDotTest
202
202
  })
203
203
  equal(p.status, 200)
204
204
  headerIs(p, CH.AcAllowOrigin, null)
@@ -208,16 +208,16 @@ await describe('CORS', async () => {
208
208
 
209
209
  await test('origin allowed', async () => {
210
210
  corsConfig = {
211
- corsOrigins: [AllowedDotCom],
211
+ corsOrigins: [AllowedDotTest],
212
212
  corsMethods: ['GET'],
213
213
  corsCredentials: true,
214
214
  corsExposedHeaders: ['x-h1', 'x-h2']
215
215
  }
216
216
  const p = await request({
217
- [CH.Origin]: AllowedDotCom
217
+ [CH.Origin]: AllowedDotTest
218
218
  })
219
219
  equal(p.status, 200)
220
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
220
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
221
221
  headerIs(p, CH.AcAllowCredentials, 'true')
222
222
  headerIs(p, CH.AcExposeHeaders, 'x-h1,x-h2')
223
223
  })
@@ -9,13 +9,8 @@ export function jwtCookie(cookieName, payload, path = '/') {
9
9
  function jwt(payload) {
10
10
  return [
11
11
  'Header_Not_In_Use',
12
- toBase64Url(payload),
12
+ Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url'),
13
13
  'Signature_Not_In_Use'
14
14
  ].join('.')
15
15
  }
16
16
 
17
- function toBase64Url(obj) {
18
- return btoa(JSON.stringify(obj))
19
- .replace('+', '-')
20
- .replace('/', '_')
21
- }
@@ -11,9 +11,13 @@ import { UNKNOWN_MIME_EXT } from '../ApiConstants.js'
11
11
  const extToMime = {
12
12
  '3g2': 'video/3gpp2',
13
13
  '3gp': 'video/3gpp',
14
+ '3mf': 'model/3mf',
14
15
  '7z': 'application/x-7z-compressed',
15
16
  aac: 'audio/aac',
16
17
  abw: 'application/x-abiword',
18
+ aif: 'audio/aiff',
19
+ aifc: 'audio/aiff',
20
+ aiff: 'audio/aiff',
17
21
  apng: 'image/apng',
18
22
  arc: 'application/x-freearc',
19
23
  avi: 'video/x-msvideo',
@@ -28,12 +32,22 @@ const extToMime = {
28
32
  csh: 'application/x-csh',
29
33
  css: 'text/css',
30
34
  csv: 'text/csv',
35
+ dae: 'model/vnd.collada+xml',
31
36
  doc: 'application/msword',
32
37
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
38
+ drc: 'model/vnd.draco',
39
+ eml: 'message/rfc822',
33
40
  eot: 'application/vnd.ms-fontobject',
34
41
  epub: 'application/epub+zip',
42
+ exe: 'application/vnd.microsoft.portable-executable',
43
+ fbx: 'application/octet-stream',
44
+ flac: 'audio/flac',
35
45
  gif: 'image/gif',
46
+ glb: 'model/gltf-binary',
47
+ gltf: 'model/gltf+json',
36
48
  gz: 'application/gzip',
49
+ heic: 'image/heic',
50
+ heif: 'image/heif',
37
51
  htm: 'text/html',
38
52
  html: 'text/html',
39
53
  ico: 'image/vnd.microsoft.icon',
@@ -44,13 +58,21 @@ const extToMime = {
44
58
  js: 'application/javascript',
45
59
  json: 'application/json',
46
60
  jsonld: 'application/ld+json',
61
+ lz: 'application/x-lzip',
62
+ m4a: 'audio/mp4',
63
+ map: 'application/json',
64
+ md: 'text/markdown',
47
65
  mid: 'audio/midi',
48
66
  midi: 'audio/midi',
49
67
  mjs: 'text/javascript',
68
+ mkv: 'video/x-matroska',
69
+ mov: 'video/quicktime',
50
70
  mp3: 'audio/mpeg',
51
71
  mp4: 'video/mp4',
52
72
  mpeg: 'video/mpeg',
53
73
  mpkg: 'application/vnd.apple.installer+xml',
74
+ mtl: 'text/plain',
75
+ obj: 'text/plain',
54
76
  odp: 'application/vnd.oasis.opendocument.presentation',
55
77
  ods: 'application/vnd.oasis.opendocument.spreadsheet',
56
78
  odt: 'application/vnd.oasis.opendocument.text',
@@ -61,19 +83,24 @@ const extToMime = {
61
83
  otf: 'font/otf',
62
84
  pdf: 'application/pdf',
63
85
  php: 'application/x-httpd-php',
86
+ ply: 'application/octet-stream',
64
87
  png: 'image/png',
65
88
  ppt: 'application/vnd.ms-powerpoint',
66
89
  pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
67
90
  rar: 'application/vnd.rar',
68
91
  rtf: 'application/rtf',
69
92
  sh: 'application/x-sh',
93
+ stl: 'model/stl',
70
94
  svg: 'image/svg+xml',
71
95
  tar: 'application/x-tar',
72
96
  tif: 'image/tiff',
73
97
  ts: 'video/mp2t',
74
98
  ttf: 'font/ttf',
75
99
  txt: 'text/plain',
100
+ usd: 'model/vnd.usd',
101
+ usdz: 'model/vnd.usdz+zip',
76
102
  vsd: 'application/vnd.visio',
103
+ wasm: 'application/wasm',
77
104
  wav: 'audio/wav',
78
105
  weba: 'audio/webm',
79
106
  webm: 'video/webm',
@@ -85,9 +112,11 @@ const extToMime = {
85
112
  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
86
113
  xml: 'application/xml',
87
114
  xul: 'application/vnd.mozilla.xul+xml',
115
+ xz: 'application/x-xz',
88
116
  yaml: 'application/yaml',
89
117
  yml: 'application/yaml',
90
- zip: 'application/zip'
118
+ zip: 'application/zip',
119
+ zst: 'application/zstd'
91
120
  }
92
121
 
93
122
  const mimeToExt = mapMimeToExt(extToMime)
@@ -105,8 +134,8 @@ export function mimeFor(filename) {
105
134
  }
106
135
  function extname(filename) {
107
136
  const i = filename.lastIndexOf('.')
108
- return i === -1
109
- ? ''
137
+ return i === -1
138
+ ? ''
110
139
  : filename.slice(i + 1)
111
140
  }
112
141