msw 2.12.11 → 2.12.13
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/config/scripts/postinstall.js +2 -2
- package/lib/core/{HttpResponse-Cw4ELwIN.d.mts → HttpResponse-Be4eT3x6.d.mts} +8 -2
- package/lib/core/{HttpResponse-CVs3ngx3.d.ts → HttpResponse-Dj6ibgFJ.d.ts} +8 -2
- package/lib/core/HttpResponse.d.mts +1 -1
- package/lib/core/HttpResponse.d.ts +1 -1
- package/lib/core/HttpResponse.js +53 -11
- package/lib/core/HttpResponse.js.map +1 -1
- package/lib/core/HttpResponse.mjs +53 -11
- package/lib/core/HttpResponse.mjs.map +1 -1
- package/lib/core/SetupApi.d.mts +1 -1
- package/lib/core/SetupApi.d.ts +1 -1
- package/lib/core/getResponse.d.mts +1 -1
- package/lib/core/getResponse.d.ts +1 -1
- package/lib/core/graphql.d.mts +1 -1
- package/lib/core/graphql.d.ts +1 -1
- package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
- package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
- package/lib/core/handlers/GraphQLHandler.js +29 -1
- package/lib/core/handlers/GraphQLHandler.js.map +1 -1
- package/lib/core/handlers/GraphQLHandler.mjs +29 -1
- package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
- package/lib/core/handlers/HttpHandler.d.mts +1 -1
- package/lib/core/handlers/HttpHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.d.mts +1 -1
- package/lib/core/handlers/RequestHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.js.map +1 -1
- package/lib/core/handlers/RequestHandler.mjs.map +1 -1
- package/lib/core/http.d.mts +1 -1
- package/lib/core/http.d.ts +1 -1
- package/lib/core/index.d.mts +3 -3
- package/lib/core/index.d.ts +3 -3
- package/lib/core/index.js +6 -4
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +8 -3
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/passthrough.d.mts +1 -1
- package/lib/core/passthrough.d.ts +1 -1
- package/lib/core/sse.d.mts +1 -1
- package/lib/core/sse.d.ts +1 -1
- package/lib/core/sse.js +1 -1
- package/lib/core/sse.js.map +1 -1
- package/lib/core/sse.mjs +1 -1
- package/lib/core/sse.mjs.map +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
- package/lib/core/utils/executeHandlers.d.mts +1 -1
- package/lib/core/utils/executeHandlers.d.ts +1 -1
- package/lib/core/utils/handleRequest.d.mts +1 -1
- package/lib/core/utils/handleRequest.d.ts +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.js +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.js.map +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.mjs +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.mjs.map +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
- package/lib/core/utils/internal/parseMultipartData.js +3 -1
- package/lib/core/utils/internal/parseMultipartData.js.map +1 -1
- package/lib/core/utils/internal/parseMultipartData.mjs +3 -1
- package/lib/core/utils/internal/parseMultipartData.mjs.map +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
- package/lib/core/utils/request/getAllAcceptedMimeTypes.d.mts +14 -0
- package/lib/core/utils/request/getAllAcceptedMimeTypes.d.ts +14 -0
- package/lib/core/utils/request/getAllAcceptedMimeTypes.js +61 -0
- package/lib/core/utils/request/getAllAcceptedMimeTypes.js.map +1 -0
- package/lib/core/utils/request/getAllAcceptedMimeTypes.mjs +41 -0
- package/lib/core/utils/request/getAllAcceptedMimeTypes.mjs.map +1 -0
- package/lib/core/ws/WebSocketIndexedDBClientStore.js +1 -1
- package/lib/core/ws/WebSocketIndexedDBClientStore.js.map +1 -1
- package/lib/core/ws/WebSocketIndexedDBClientStore.mjs +1 -1
- package/lib/core/ws/WebSocketIndexedDBClientStore.mjs.map +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
- package/lib/iife/index.js +262 -159
- package/lib/iife/index.js.map +1 -1
- package/lib/mockServiceWorker.js +1 -1
- package/package.json +2 -2
- package/src/core/HttpResponse.test.ts +25 -9
- package/src/core/HttpResponse.ts +62 -10
- package/src/core/handlers/GraphQLHandler.ts +54 -2
- package/src/core/handlers/RequestHandler.ts +1 -1
- package/src/core/index.ts +8 -3
- package/src/core/sse.ts +1 -1
- package/src/core/utils/internal/parseGraphQLRequest.ts +1 -1
- package/src/core/utils/internal/parseMultipartData.ts +3 -1
- package/src/core/utils/request/getAllAcceptedMimeTypes.test.ts +86 -0
- package/src/core/utils/request/getAllAcceptedMimeTypes.ts +70 -0
- package/src/core/ws/WebSocketIndexedDBClientStore.ts +1 -2
package/lib/mockServiceWorker.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Please do NOT modify this file.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const PACKAGE_VERSION = '2.12.
|
|
10
|
+
const PACKAGE_VERSION = '2.12.13'
|
|
11
11
|
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
|
|
12
12
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
|
13
13
|
const activeClientIds = new Set()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "msw",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.13",
|
|
4
4
|
"description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./lib/core/index.js",
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
"vitest-environment-miniflare": "^2.14.4",
|
|
286
286
|
"webpack": "^5.95.0",
|
|
287
287
|
"webpack-http-server": "^0.5.0",
|
|
288
|
-
"msw": "2.12.
|
|
288
|
+
"msw": "2.12.13"
|
|
289
289
|
},
|
|
290
290
|
"peerDependencies": {
|
|
291
291
|
"typescript": ">= 4.8.x"
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* @vitest-environment node
|
|
3
3
|
*/
|
|
4
4
|
import { TextEncoder } from 'util'
|
|
5
|
-
import { HttpResponse } from './HttpResponse'
|
|
5
|
+
import { HttpResponse, kDefaultContentType } from './HttpResponse'
|
|
6
6
|
|
|
7
7
|
it('creates a plain response', async () => {
|
|
8
8
|
const response = new HttpResponse(null, { status: 301 })
|
|
9
9
|
expect(response.status).toBe(301)
|
|
10
10
|
expect(response.statusText).toBe('Moved Permanently')
|
|
11
11
|
expect(response.body).toBe(null)
|
|
12
|
-
expect(
|
|
12
|
+
await expect(response.text()).resolves.toBe('')
|
|
13
13
|
expect(Object.fromEntries(response.headers.entries())).toEqual({})
|
|
14
14
|
})
|
|
15
15
|
|
|
@@ -24,11 +24,12 @@ describe('HttpResponse.text()', () => {
|
|
|
24
24
|
expect(response.status).toBe(201)
|
|
25
25
|
expect(response.statusText).toBe('Created')
|
|
26
26
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
27
|
-
expect(
|
|
27
|
+
await expect(response.text()).resolves.toBe('hello world')
|
|
28
28
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
29
29
|
'content-length': '11',
|
|
30
30
|
'content-type': 'text/plain',
|
|
31
31
|
})
|
|
32
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
32
33
|
})
|
|
33
34
|
|
|
34
35
|
it('creates a text response with special characters', async () => {
|
|
@@ -37,7 +38,7 @@ describe('HttpResponse.text()', () => {
|
|
|
37
38
|
expect(response.status).toBe(201)
|
|
38
39
|
expect(response.statusText).toBe('Created')
|
|
39
40
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
40
|
-
expect(
|
|
41
|
+
await expect(response.text()).resolves.toBe('안녕 세상')
|
|
41
42
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
42
43
|
'content-length': '13',
|
|
43
44
|
'content-type': 'text/plain',
|
|
@@ -52,11 +53,12 @@ describe('HttpResponse.text()', () => {
|
|
|
52
53
|
expect(response.status).toBe(200)
|
|
53
54
|
expect(response.statusText).toBe('OK')
|
|
54
55
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
55
|
-
expect(
|
|
56
|
+
await expect(response.text()).resolves.toBe('hello world')
|
|
56
57
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
57
58
|
'content-length': '11',
|
|
58
59
|
'content-type': 'text/plain; charset=utf-8',
|
|
59
60
|
})
|
|
61
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
60
62
|
})
|
|
61
63
|
|
|
62
64
|
it('allows overriding the "Content-Length" response header', async () => {
|
|
@@ -83,6 +85,7 @@ describe('HttpResponse.json()', () => {
|
|
|
83
85
|
'content-length': '20',
|
|
84
86
|
'content-type': 'application/json',
|
|
85
87
|
})
|
|
88
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
86
89
|
})
|
|
87
90
|
|
|
88
91
|
it('creates a json response given an object with special characters', async () => {
|
|
@@ -172,6 +175,7 @@ describe('HttpResponse.json()', () => {
|
|
|
172
175
|
},
|
|
173
176
|
)
|
|
174
177
|
|
|
178
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
175
179
|
expect(response.status).toBe(200)
|
|
176
180
|
expect(response.statusText).toBe('OK')
|
|
177
181
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
@@ -201,13 +205,15 @@ describe('HttpResponse.xml()', () => {
|
|
|
201
205
|
it('creates an xml response', async () => {
|
|
202
206
|
const response = HttpResponse.xml('<user name="John" />')
|
|
203
207
|
|
|
208
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
204
209
|
expect(response.status).toBe(200)
|
|
205
210
|
expect(response.statusText).toBe('OK')
|
|
206
211
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
207
|
-
expect(
|
|
212
|
+
await expect(response.text()).resolves.toBe('<user name="John" />')
|
|
208
213
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
209
214
|
'content-type': 'text/xml',
|
|
210
215
|
})
|
|
216
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
211
217
|
})
|
|
212
218
|
|
|
213
219
|
it('allows overriding the "Content-Type" response header', async () => {
|
|
@@ -220,10 +226,11 @@ describe('HttpResponse.xml()', () => {
|
|
|
220
226
|
expect(response.status).toBe(200)
|
|
221
227
|
expect(response.statusText).toBe('OK')
|
|
222
228
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
223
|
-
expect(
|
|
229
|
+
await expect(response.text()).resolves.toBe('<user name="John" />')
|
|
224
230
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
225
231
|
'content-type': 'text/xml; charset=utf-8',
|
|
226
232
|
})
|
|
233
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
227
234
|
})
|
|
228
235
|
})
|
|
229
236
|
|
|
@@ -234,10 +241,13 @@ describe('HttpResponse.html()', () => {
|
|
|
234
241
|
expect(response.status).toBe(200)
|
|
235
242
|
expect(response.statusText).toBe('OK')
|
|
236
243
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
237
|
-
expect(
|
|
244
|
+
await expect(response.text()).resolves.toBe(
|
|
245
|
+
'<p class="author">Jane Doe</p>',
|
|
246
|
+
)
|
|
238
247
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
239
248
|
'content-type': 'text/html',
|
|
240
249
|
})
|
|
250
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
241
251
|
})
|
|
242
252
|
|
|
243
253
|
it('allows overriding the "Content-Type" response header', async () => {
|
|
@@ -250,10 +260,13 @@ describe('HttpResponse.html()', () => {
|
|
|
250
260
|
expect(response.status).toBe(200)
|
|
251
261
|
expect(response.statusText).toBe('OK')
|
|
252
262
|
expect(response.body).toBeInstanceOf(ReadableStream)
|
|
253
|
-
expect(
|
|
263
|
+
await expect(response.text()).resolves.toBe(
|
|
264
|
+
'<p class="author">Jane Doe</p>',
|
|
265
|
+
)
|
|
254
266
|
expect(Object.fromEntries(response.headers.entries())).toEqual({
|
|
255
267
|
'content-type': 'text/html; charset=utf-8',
|
|
256
268
|
})
|
|
269
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
257
270
|
})
|
|
258
271
|
})
|
|
259
272
|
|
|
@@ -272,6 +285,7 @@ describe('HttpResponse.arrayBuffer()', () => {
|
|
|
272
285
|
'content-length': '11',
|
|
273
286
|
'content-type': 'application/octet-stream',
|
|
274
287
|
})
|
|
288
|
+
expect(kDefaultContentType in response).toBe(true)
|
|
275
289
|
})
|
|
276
290
|
|
|
277
291
|
it('allows overriding the "Content-Type" response header', async () => {
|
|
@@ -292,6 +306,7 @@ describe('HttpResponse.arrayBuffer()', () => {
|
|
|
292
306
|
'content-length': '11',
|
|
293
307
|
'content-type': 'text/plain; charset=utf-8',
|
|
294
308
|
})
|
|
309
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
295
310
|
})
|
|
296
311
|
|
|
297
312
|
it('creates an array buffer response from a shared array buffer', async () => {
|
|
@@ -340,6 +355,7 @@ describe('HttpResponse.arrayBuffer()', () => {
|
|
|
340
355
|
'content-length': '11',
|
|
341
356
|
'content-type': 'text/plain; charset=utf-8',
|
|
342
357
|
})
|
|
358
|
+
expect(kDefaultContentType in response).toBe(false)
|
|
343
359
|
})
|
|
344
360
|
})
|
|
345
361
|
|
package/src/core/HttpResponse.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface StrictRequest<BodyType extends JsonBodyType> extends Request {
|
|
|
28
28
|
export type StrictResponse<BodyType extends DefaultBodyType> =
|
|
29
29
|
HttpResponse<BodyType>
|
|
30
30
|
|
|
31
|
+
export const kDefaultContentType = Symbol.for('kDefaultContentType')
|
|
32
|
+
|
|
31
33
|
/**
|
|
32
34
|
* A drop-in replacement for the standard `Response` class
|
|
33
35
|
* to allow additional features, like mocking the response `Set-Cookie` header.
|
|
@@ -65,8 +67,9 @@ export class HttpResponse<
|
|
|
65
67
|
init?: HttpResponseInit,
|
|
66
68
|
): HttpResponse<BodyType> {
|
|
67
69
|
const responseInit = normalizeResponseInit(init)
|
|
70
|
+
const hasExplicitContentType = responseInit.headers.has('Content-Type')
|
|
68
71
|
|
|
69
|
-
if (!
|
|
72
|
+
if (!hasExplicitContentType) {
|
|
70
73
|
responseInit.headers.set('Content-Type', 'text/plain')
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -80,7 +83,16 @@ export class HttpResponse<
|
|
|
80
83
|
)
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
const response = new HttpResponse(body, responseInit)
|
|
87
|
+
|
|
88
|
+
if (!hasExplicitContentType) {
|
|
89
|
+
Object.defineProperty(response, kDefaultContentType, {
|
|
90
|
+
value: true,
|
|
91
|
+
enumerable: false,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return response
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
/**
|
|
@@ -94,8 +106,9 @@ export class HttpResponse<
|
|
|
94
106
|
init?: HttpResponseInit,
|
|
95
107
|
): HttpResponse<BodyType> {
|
|
96
108
|
const responseInit = normalizeResponseInit(init)
|
|
109
|
+
const hasExplicitContentType = responseInit.headers.has('Content-Type')
|
|
97
110
|
|
|
98
|
-
if (!
|
|
111
|
+
if (!hasExplicitContentType) {
|
|
99
112
|
responseInit.headers.set('Content-Type', 'application/json')
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -112,7 +125,16 @@ export class HttpResponse<
|
|
|
112
125
|
)
|
|
113
126
|
}
|
|
114
127
|
|
|
115
|
-
|
|
128
|
+
const response = new HttpResponse(responseText, responseInit)
|
|
129
|
+
|
|
130
|
+
if (!hasExplicitContentType) {
|
|
131
|
+
Object.defineProperty(response, kDefaultContentType, {
|
|
132
|
+
value: true,
|
|
133
|
+
enumerable: false,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return response as HttpResponse<BodyType>
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
/**
|
|
@@ -126,12 +148,22 @@ export class HttpResponse<
|
|
|
126
148
|
init?: HttpResponseInit,
|
|
127
149
|
): HttpResponse<BodyType> {
|
|
128
150
|
const responseInit = normalizeResponseInit(init)
|
|
151
|
+
const hasExplicitContentType = responseInit.headers.has('Content-Type')
|
|
129
152
|
|
|
130
|
-
if (!
|
|
153
|
+
if (!hasExplicitContentType) {
|
|
131
154
|
responseInit.headers.set('Content-Type', 'text/xml')
|
|
132
155
|
}
|
|
133
156
|
|
|
134
|
-
|
|
157
|
+
const response = new HttpResponse(body, responseInit)
|
|
158
|
+
|
|
159
|
+
if (!hasExplicitContentType) {
|
|
160
|
+
Object.defineProperty(response, kDefaultContentType, {
|
|
161
|
+
value: true,
|
|
162
|
+
enumerable: false,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return response as HttpResponse<BodyType>
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
/**
|
|
@@ -145,12 +177,22 @@ export class HttpResponse<
|
|
|
145
177
|
init?: HttpResponseInit,
|
|
146
178
|
): HttpResponse<BodyType> {
|
|
147
179
|
const responseInit = normalizeResponseInit(init)
|
|
180
|
+
const hasExplicitContentType = responseInit.headers.has('Content-Type')
|
|
148
181
|
|
|
149
|
-
if (!
|
|
182
|
+
if (!hasExplicitContentType) {
|
|
150
183
|
responseInit.headers.set('Content-Type', 'text/html')
|
|
151
184
|
}
|
|
152
185
|
|
|
153
|
-
|
|
186
|
+
const response = new HttpResponse(body, responseInit)
|
|
187
|
+
|
|
188
|
+
if (!hasExplicitContentType) {
|
|
189
|
+
Object.defineProperty(response, kDefaultContentType, {
|
|
190
|
+
value: true,
|
|
191
|
+
enumerable: false,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return response as HttpResponse<BodyType>
|
|
154
196
|
}
|
|
155
197
|
|
|
156
198
|
/**
|
|
@@ -167,8 +209,9 @@ export class HttpResponse<
|
|
|
167
209
|
init?: HttpResponseInit,
|
|
168
210
|
): HttpResponse<BodyType> {
|
|
169
211
|
const responseInit = normalizeResponseInit(init)
|
|
212
|
+
const hasExplicitContentType = responseInit.headers.has('Content-Type')
|
|
170
213
|
|
|
171
|
-
if (!
|
|
214
|
+
if (!hasExplicitContentType) {
|
|
172
215
|
responseInit.headers.set('Content-Type', 'application/octet-stream')
|
|
173
216
|
}
|
|
174
217
|
|
|
@@ -176,7 +219,16 @@ export class HttpResponse<
|
|
|
176
219
|
responseInit.headers.set('Content-Length', body.byteLength.toString())
|
|
177
220
|
}
|
|
178
221
|
|
|
179
|
-
|
|
222
|
+
const response = new HttpResponse(body, responseInit)
|
|
223
|
+
|
|
224
|
+
if (!hasExplicitContentType) {
|
|
225
|
+
Object.defineProperty(response, kDefaultContentType, {
|
|
226
|
+
value: true,
|
|
227
|
+
enumerable: false,
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return response as HttpResponse<BodyType>
|
|
180
232
|
}
|
|
181
233
|
|
|
182
234
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { invariant } from 'outvariant'
|
|
1
2
|
import {
|
|
2
3
|
parse,
|
|
3
4
|
type DocumentNode,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
DefaultBodyType,
|
|
9
10
|
RequestHandler,
|
|
10
11
|
RequestHandlerDefaultInfo,
|
|
12
|
+
RequestHandlerExecutionResult,
|
|
11
13
|
RequestHandlerOptions,
|
|
12
14
|
ResponseResolver,
|
|
13
15
|
} from './RequestHandler'
|
|
@@ -26,7 +28,9 @@ import {
|
|
|
26
28
|
import { toPublicUrl } from '../utils/request/toPublicUrl'
|
|
27
29
|
import { devUtils } from '../utils/internal/devUtils'
|
|
28
30
|
import { getAllRequestCookies } from '../utils/request/getRequestCookies'
|
|
29
|
-
import {
|
|
31
|
+
import { ResponseResolutionContext } from 'src/iife'
|
|
32
|
+
import { kDefaultContentType, StrictRequest } from '../HttpResponse'
|
|
33
|
+
import { getAllAcceptedMimeTypes } from '../utils/request/getAllAcceptedMimeTypes'
|
|
30
34
|
|
|
31
35
|
export interface DocumentTypeDecoration<
|
|
32
36
|
Result = { [key: string]: any },
|
|
@@ -120,7 +124,7 @@ export function isDocumentNode(
|
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
function isDocumentTypeDecoration(
|
|
123
|
-
value:
|
|
127
|
+
value: unknown,
|
|
124
128
|
): value is DocumentTypeDecoration<any, any> {
|
|
125
129
|
return value instanceof String
|
|
126
130
|
}
|
|
@@ -309,6 +313,54 @@ Consider naming this operation or using "graphql.operation()" request handler to
|
|
|
309
313
|
)
|
|
310
314
|
}
|
|
311
315
|
|
|
316
|
+
public async run(args: {
|
|
317
|
+
request: StrictRequest<any>
|
|
318
|
+
requestId: string
|
|
319
|
+
resolutionContext?: ResponseResolutionContext
|
|
320
|
+
}): Promise<RequestHandlerExecutionResult<GraphQLRequestParsedResult> | null> {
|
|
321
|
+
const result = await super.run(args)
|
|
322
|
+
|
|
323
|
+
if (result?.response == null) {
|
|
324
|
+
return result
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!(kDefaultContentType in result.response)) {
|
|
328
|
+
return result
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const acceptedMimeTypes = getAllAcceptedMimeTypes(
|
|
332
|
+
args.request.headers.get('accept'),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if (acceptedMimeTypes.length === 0) {
|
|
336
|
+
return result
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const graphqlResponseIndex = acceptedMimeTypes.indexOf(
|
|
340
|
+
'application/graphql-response+json',
|
|
341
|
+
)
|
|
342
|
+
const jsonIndex = acceptedMimeTypes.indexOf('application/json')
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Use the "application/graphql-response+json" response content type
|
|
346
|
+
* only when the client accepts it AND prefers it over "application/json"
|
|
347
|
+
* (i.e. it appears earlier in the precedence-sorted list, or "application/json"
|
|
348
|
+
* is not listed at all).
|
|
349
|
+
* @see https://github.com/graphql/graphql-over-http/blob/4d1df1fb829ec2dd3ecbf3c6aa4025bd356c270d/spec/GraphQLOverHTTP.md#accept
|
|
350
|
+
*/
|
|
351
|
+
if (
|
|
352
|
+
graphqlResponseIndex !== -1 &&
|
|
353
|
+
(jsonIndex === -1 || graphqlResponseIndex <= jsonIndex)
|
|
354
|
+
) {
|
|
355
|
+
result.response.headers.set(
|
|
356
|
+
'content-type',
|
|
357
|
+
'application/graphql-response+json',
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return result
|
|
362
|
+
}
|
|
363
|
+
|
|
312
364
|
private async matchOperationName(args: {
|
|
313
365
|
request: Request
|
|
314
366
|
parsedResult: GraphQLRequestParsedResult
|
|
@@ -113,7 +113,7 @@ export interface RequestHandlerOptions {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export interface RequestHandlerExecutionResult<
|
|
116
|
-
ParsedResult extends
|
|
116
|
+
ParsedResult extends object | undefined,
|
|
117
117
|
> {
|
|
118
118
|
handler: RequestHandler
|
|
119
119
|
parsedResult?: ParsedResult
|
package/src/core/index.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type AnyHandler = HttpHandler | GraphQLHandler | WebSocketHandler
|
|
|
34
34
|
|
|
35
35
|
/* Utils */
|
|
36
36
|
export { matchRequestUrl } from './utils/matching/matchRequestUrl'
|
|
37
|
-
export
|
|
37
|
+
export { handleRequest, type HandleRequestOptions } from './utils/handleRequest'
|
|
38
38
|
export {
|
|
39
39
|
onUnhandledRequest,
|
|
40
40
|
type UnhandledRequestStrategy,
|
|
@@ -92,8 +92,13 @@ export type { Path, PathParams, Match } from './utils/matching/matchRequestUrl'
|
|
|
92
92
|
export type { ParsedGraphQLRequest } from './utils/internal/parseGraphQLRequest'
|
|
93
93
|
export type { ResponseResolutionContext } from './utils/executeHandlers'
|
|
94
94
|
|
|
95
|
-
export
|
|
96
|
-
|
|
95
|
+
export {
|
|
96
|
+
HttpResponse,
|
|
97
|
+
type HttpResponseInit,
|
|
98
|
+
type StrictRequest,
|
|
99
|
+
type StrictResponse,
|
|
100
|
+
} from './HttpResponse'
|
|
101
|
+
export { delay, type DelayMode } from './delay'
|
|
97
102
|
export { bypass } from './bypass'
|
|
98
103
|
export { passthrough } from './passthrough'
|
|
99
104
|
export { isCommonAssetRequest } from './isCommonAssetRequest'
|
package/src/core/sse.ts
CHANGED
|
@@ -84,7 +84,7 @@ function extractMultipartVariables<VariablesType extends GraphQLVariables>(
|
|
|
84
84
|
|
|
85
85
|
for (const path of paths) {
|
|
86
86
|
if (!(path in target)) {
|
|
87
|
-
throw new Error(`Property '${
|
|
87
|
+
throw new Error(`Property '${path}' is not in operations.`)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
target = target[path]
|
|
@@ -62,7 +62,9 @@ export function parseMultipartData<T extends DefaultRequestMultipartBody>(
|
|
|
62
62
|
return undefined
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const boundaryRegExp = new RegExp(
|
|
65
|
+
const boundaryRegExp = new RegExp(
|
|
66
|
+
`--+${boundary.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
|
|
67
|
+
)
|
|
66
68
|
const fields = data
|
|
67
69
|
.split(boundaryRegExp)
|
|
68
70
|
.filter((chunk) => chunk.startsWith('\r\n') && chunk.endsWith('\r\n'))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { getAllAcceptedMimeTypes } from './getAllAcceptedMimeTypes'
|
|
2
|
+
|
|
3
|
+
it('returns an empty array for null accept header', () => {
|
|
4
|
+
expect(getAllAcceptedMimeTypes(null)).toEqual([])
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
it('returns a single mime type as-is', () => {
|
|
8
|
+
expect(getAllAcceptedMimeTypes('application/json')).toEqual([
|
|
9
|
+
'application/json',
|
|
10
|
+
])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('returns multiple mime types in order', () => {
|
|
14
|
+
expect(getAllAcceptedMimeTypes('text/html, application/json')).toEqual([
|
|
15
|
+
'text/html',
|
|
16
|
+
'application/json',
|
|
17
|
+
])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('sorts by quality value (q parameter)', () => {
|
|
21
|
+
expect(
|
|
22
|
+
getAllAcceptedMimeTypes('text/plain;q=0.5, application/json;q=0.9'),
|
|
23
|
+
).toEqual(['application/json', 'text/plain'])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('excludes types with q=0', () => {
|
|
27
|
+
expect(getAllAcceptedMimeTypes('text/html, text/plain;q=0')).toEqual([
|
|
28
|
+
'text/html',
|
|
29
|
+
])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('returns an empty array when all types have q=0', () => {
|
|
33
|
+
expect(getAllAcceptedMimeTypes('text/html;q=0, text/plain;q=0')).toEqual([])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('treats missing q as q=1 (default)', () => {
|
|
37
|
+
expect(getAllAcceptedMimeTypes('text/plain;q=0.5, application/json')).toEqual(
|
|
38
|
+
['application/json', 'text/plain'],
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('sorts by specificity when quality is equal (type/subtype > type/* > */*)', () => {
|
|
43
|
+
expect(getAllAcceptedMimeTypes('*/*, text/*, text/html')).toEqual([
|
|
44
|
+
'text/html',
|
|
45
|
+
'text/*',
|
|
46
|
+
'*/*',
|
|
47
|
+
])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('sorts by parameter count when quality and specificity are equal', () => {
|
|
51
|
+
expect(
|
|
52
|
+
getAllAcceptedMimeTypes(
|
|
53
|
+
'text/plain;format=fixed;charset=utf-8, text/plain;charset=utf-8',
|
|
54
|
+
),
|
|
55
|
+
).toEqual(['text/plain', 'text/plain'])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('applies full precedence: quality > specificity > parameter count', () => {
|
|
59
|
+
expect(
|
|
60
|
+
getAllAcceptedMimeTypes(
|
|
61
|
+
'text/*;q=0.8, application/json, text/html;q=0.8, */*;q=0.1',
|
|
62
|
+
),
|
|
63
|
+
).toEqual(['application/json', 'text/html', 'text/*', '*/*'])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('handles whitespace around values', () => {
|
|
67
|
+
expect(
|
|
68
|
+
getAllAcceptedMimeTypes(' text/html , application/json ; q=0.9 '),
|
|
69
|
+
).toEqual(['text/html', 'application/json'])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('handles a realistic browser accept header', () => {
|
|
73
|
+
expect(
|
|
74
|
+
getAllAcceptedMimeTypes(
|
|
75
|
+
'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',
|
|
76
|
+
),
|
|
77
|
+
).toEqual(['text/html', 'application/xhtml+xml', 'application/xml', '*/*'])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('handles the graphql-over-http accept header', () => {
|
|
81
|
+
expect(
|
|
82
|
+
getAllAcceptedMimeTypes(
|
|
83
|
+
'application/graphql-response+json, application/json',
|
|
84
|
+
),
|
|
85
|
+
).toEqual(['application/graphql-response+json', 'application/json'])
|
|
86
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns all accepted mime types, ordered by precedence as defined
|
|
3
|
+
* in [RFC 7231 Section 5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2).
|
|
4
|
+
*
|
|
5
|
+
* Precedence rules (highest to lowest):
|
|
6
|
+
* 1. Quality value (`q` parameter, default 1).
|
|
7
|
+
* 2. Specificity: `type/subtype` > `type/*` > `*\/*`.
|
|
8
|
+
* 3. Number of media type parameters (more = more specific).
|
|
9
|
+
*
|
|
10
|
+
* Types with `q=0` are excluded (explicitly not acceptable).
|
|
11
|
+
*/
|
|
12
|
+
export function getAllAcceptedMimeTypes(
|
|
13
|
+
acceptHeader: string | null,
|
|
14
|
+
): Array<string> {
|
|
15
|
+
if (acceptHeader == null) {
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const accepted: Array<{
|
|
20
|
+
type: string
|
|
21
|
+
quality: number
|
|
22
|
+
specificity: number
|
|
23
|
+
parameterCount: number
|
|
24
|
+
}> = []
|
|
25
|
+
|
|
26
|
+
for (const part of acceptHeader.split(',')) {
|
|
27
|
+
const [type, ...params] = part.split(';').map((v) => v.trim())
|
|
28
|
+
|
|
29
|
+
let quality = 1
|
|
30
|
+
let parameterCount = 0
|
|
31
|
+
|
|
32
|
+
for (const param of params) {
|
|
33
|
+
const [key, value] = param.split('=').map((v) => v.trim())
|
|
34
|
+
|
|
35
|
+
if (key === 'q') {
|
|
36
|
+
quality = Number(value)
|
|
37
|
+
} else {
|
|
38
|
+
parameterCount++
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// RFC 7231: a quality value of 0 indicates "not acceptable".
|
|
43
|
+
if (quality === 0) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [mediaType, mediaSubtype] = type.split('/')
|
|
48
|
+
const specificity = mediaType === '*' ? 0 : mediaSubtype === '*' ? 1 : 2
|
|
49
|
+
|
|
50
|
+
accepted.push({ type, quality, specificity, parameterCount })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!accepted.length) {
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return accepted
|
|
58
|
+
.sort((left, right) => {
|
|
59
|
+
if (right.quality !== left.quality) {
|
|
60
|
+
return right.quality - left.quality
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (right.specificity !== left.specificity) {
|
|
64
|
+
return right.specificity - left.specificity
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return right.parameterCount - left.parameterCount
|
|
68
|
+
})
|
|
69
|
+
.map((entry) => entry.type)
|
|
70
|
+
}
|
|
@@ -56,8 +56,7 @@ export class WebSocketIndexedDBClientStore implements WebSocketClientStore {
|
|
|
56
56
|
promise.resolve(request.result)
|
|
57
57
|
}
|
|
58
58
|
request.onerror = () => {
|
|
59
|
-
|
|
60
|
-
console.log(request.error)
|
|
59
|
+
console.error(request.error)
|
|
61
60
|
promise.reject(
|
|
62
61
|
new Error(
|
|
63
62
|
`Failed to get all WebSocket clients. There is likely an additional output above.`,
|