keq 2.6.8 → 2.6.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.6.10](https://github.com/keq-request/keq/compare/v2.6.9...v2.6.10) (2024-06-27)
6
+
7
+
8
+ ### Performance Improvements
9
+
10
+ * optimize the error message of the wrong header ([92f4aa6](https://github.com/keq-request/keq/commit/92f4aa6d58d258521ee0a2913a8cfb52597e9ed8))
11
+
12
+ ## [2.6.9](https://github.com/keq-request/keq/compare/v2.6.8...v2.6.9) (2024-06-24)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * cannot find buffer in the browser ([49b57d8](https://github.com/keq-request/keq/commit/49b57d8a13c576ab1b8e56a1ddc73ffb7dea1286))
18
+
5
19
  ## [2.6.8](https://github.com/keq-request/keq/compare/v2.6.7...v2.6.8) (2024-06-04)
6
20
 
7
21
 
@@ -0,0 +1,110 @@
1
+ import { expect, jest, test } from '@jest/globals'
2
+ import { request } from './request.js'
3
+
4
+
5
+ test('abort flowController request', async () => {
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ const mockedFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => new Promise((resolve, reject) => {
8
+ let finished = false
9
+
10
+ if (init?.signal) {
11
+ const signal = init.signal
12
+ signal.onabort = () => {
13
+ if (finished) return
14
+ finished = true
15
+ if (signal.reason) reject(signal.reason)
16
+ reject(new DOMException('AbortError', 'AbortError'))
17
+ }
18
+ }
19
+
20
+ // sleet 500ms
21
+ setTimeout(
22
+ () => {
23
+ if (finished) return
24
+ finished = true
25
+
26
+ resolve(new Response(
27
+ JSON.stringify({ code: '200' }),
28
+ {
29
+ headers: {
30
+ 'content-type': 'application/json',
31
+ },
32
+ }
33
+ ))
34
+ },
35
+ 100,
36
+ )
37
+ }))
38
+
39
+ async function abortRequest(): Promise<void> {
40
+ try {
41
+ await request
42
+ .get('http://test.com')
43
+ .option('fetchAPI', mockedFetch)
44
+ .flowControl('abort', 'test')
45
+ } catch (e) {
46
+ expect(e).toBeInstanceOf(DOMException)
47
+ expect((e as DOMException).name).toBe('AbortError')
48
+ }
49
+ }
50
+
51
+ void abortRequest()
52
+
53
+ await request
54
+ .get('http://test.com')
55
+ .option('fetchAPI', mockedFetch)
56
+ .flowControl('abort', 'test')
57
+
58
+ expect(mockedFetch).toBeCalledTimes(2)
59
+ })
60
+
61
+ test('serial flowController request', async () => {
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
+ const mockedFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => new Promise((resolve, reject) => {
64
+ let finished = false
65
+
66
+ if (init?.signal) {
67
+ const signal = init.signal
68
+ signal.onabort = () => {
69
+ if (finished) return
70
+ finished = true
71
+ if (signal.reason) reject(signal.reason)
72
+ reject(new DOMException('AbortError', 'AbortError'))
73
+ }
74
+ }
75
+
76
+ // sleet 500ms
77
+ setTimeout(
78
+ () => {
79
+ if (finished) return
80
+ finished = true
81
+
82
+ resolve(new Response(
83
+ JSON.stringify({ code: '200' }),
84
+ {
85
+ headers: {
86
+ 'content-type': 'application/json',
87
+ },
88
+ }
89
+ ))
90
+ },
91
+ 100,
92
+ )
93
+ }))
94
+
95
+ async function serialRequest(): Promise<void> {
96
+ await request
97
+ .get('http://test.com')
98
+ .option('fetchAPI', mockedFetch)
99
+ .flowControl('serial', 'test')
100
+ }
101
+
102
+ void serialRequest()
103
+
104
+ await request
105
+ .get('http://test.com')
106
+ .option('fetchAPI', mockedFetch)
107
+ .flowControl('abort', 'test')
108
+
109
+ expect(mockedFetch).toBeCalledTimes(2)
110
+ })
@@ -0,0 +1,51 @@
1
+ /* eslint-disable @typescript-eslint/ban-types */
2
+ import { createRequest } from '~/index.js'
3
+
4
+ import type { KeqBaseOperation, KeqOperations } from '~/types/keq-operation.js'
5
+
6
+
7
+ interface TestModule extends KeqOperations {
8
+ 'http://test.com': {
9
+ get: {
10
+ requestParams: {}
11
+ requestQuery: {
12
+ q: string
13
+ }
14
+ requestHeaders: KeqBaseOperation['requestHeaders']
15
+ requestBody: {}
16
+ responseBody: {
17
+ data: 'test get'
18
+ }
19
+ }
20
+ post: {
21
+ requestParams: {}
22
+ requestQuery: {
23
+ q: string
24
+ }
25
+ requestHeaders: {
26
+ 'x-test': 'test'
27
+ }
28
+ requestBody: {
29
+ id: number
30
+ name: string
31
+ file: Buffer
32
+ }
33
+ responseBody: {
34
+ data: 'test post'
35
+ }
36
+ }
37
+ }
38
+ 'http://example.com': {
39
+ get: {
40
+ requestParams: {}
41
+ requestQuery: {}
42
+ requestHeaders: KeqBaseOperation['requestHeaders']
43
+ requestBody: {}
44
+ responseBody: {
45
+ data: 'example get'
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export const request = createRequest<TestModule>()
@@ -0,0 +1,48 @@
1
+ import { expect, test } from '@jest/globals'
2
+ import { request } from './request.js'
3
+
4
+
5
+ test('resolve response body by text', async () => {
6
+ const result = await request
7
+ .get('http://test.com')
8
+ .auth('username', 'password')
9
+ .resolveWith('text')
10
+
11
+ expect(result).toEqual('{"code":"200"}')
12
+ })
13
+
14
+ test('resolve response body by json', async () => {
15
+ const result = await request
16
+ .get('http://test.com')
17
+ .auth('username', 'password')
18
+ .resolveWith<{ code: string }>('json')
19
+
20
+ expect(result).toEqual({ code: '200' })
21
+ })
22
+
23
+ test('resolve response body by blob', async () => {
24
+ const result = await request
25
+ .get('http://test.com')
26
+ .auth('username', 'password')
27
+ .resolveWith('blob')
28
+
29
+ expect(result).toBeInstanceOf(Blob)
30
+ })
31
+
32
+ test('resolve response body by arrayBuffer', async () => {
33
+ const result = await request
34
+ .get('http://test.com')
35
+ .auth('username', 'password')
36
+ .resolveWith('array-buffer')
37
+
38
+ expect(result).toBeInstanceOf(ArrayBuffer)
39
+ })
40
+
41
+ test('resolve response body', async () => {
42
+ const result = await request
43
+ .get('http://test.com')
44
+ .auth('username', 'password')
45
+ .resolveWith('response')
46
+
47
+ expect(result).toBeInstanceOf(Response)
48
+ })
@@ -0,0 +1,35 @@
1
+ import { expect, jest, test } from '@jest/globals'
2
+ import { KeqRetryOn } from '~/index.js'
3
+ import { request } from './request.js'
4
+
5
+
6
+ test('send request retry twice', async () => {
7
+ const mockedFetch = jest.fn()
8
+ const retryOn = jest.fn<KeqRetryOn>(() => true)
9
+ const mockedListener = jest.fn()
10
+
11
+ await request
12
+ .get('http://test.com')
13
+ .retry(2, 10, retryOn)
14
+ .option('fetchAPI', mockedFetch)
15
+ .on('retry', mockedListener)
16
+
17
+ expect(mockedFetch.mock.calls).toHaveLength(3)
18
+ expect(mockedListener.mock.calls).toHaveLength(2)
19
+
20
+ expect(retryOn.mock.calls.length).toBe(2)
21
+ expect(retryOn.mock.calls[0][0]).toBe(0)
22
+ expect(retryOn.mock.calls[1][0]).toBe(1)
23
+ expect(retryOn.mock.calls[1][2].retry?.delay).toBe(10)
24
+ })
25
+
26
+ test('send request retry once', async () => {
27
+ const mockedFetch = jest.fn()
28
+
29
+ await request
30
+ .get('http://test.com')
31
+ .retry(2, 0)
32
+ .option('fetchAPI', mockedFetch)
33
+
34
+ expect(mockedFetch.mock.calls).toHaveLength(1)
35
+ })
@@ -0,0 +1,107 @@
1
+ import { describe, expect, test } from '@jest/globals'
2
+ import { Mock } from 'jest-mock'
3
+ import { request } from './request.js'
4
+
5
+
6
+ describe('send multipart/form-data request', () => {
7
+ test('use FormData Class', async () => {
8
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
9
+
10
+ const formData = new FormData()
11
+
12
+ const resumeFile = new Blob(['test'], { type: 'text/plain' })
13
+ formData.append('name', 'John')
14
+ formData.append('resume', resumeFile, 'test.txt')
15
+ formData.append('friends', 'Tom')
16
+ formData.append('friends', 'Bob')
17
+
18
+ await request
19
+ .post('http://test.com')
20
+ .send(formData)
21
+
22
+ const init = mockedFetch.mock.calls[0][1]
23
+ expect(init).toBeDefined()
24
+
25
+ const headers = init?.headers as Headers
26
+
27
+ // FormData 需要自动删除 Content-Type
28
+ expect(headers.get('Content-Type')).toBeNull()
29
+
30
+ const body = init?.body as FormData
31
+
32
+ expect(body.getAll('name')).toEqual(['John'])
33
+ expect(body.getAll('friends')).toEqual(['Tom', 'Bob'])
34
+ expect((body.get('resume') as File).name).toBe('test.txt')
35
+ })
36
+
37
+ test('use keq API', async () => {
38
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
39
+
40
+ await request
41
+ .post('http://test.com')
42
+ .field('name', 'John')
43
+ .field('friends', ['Tom', 'Bob'])
44
+ .field({ age: '12' })
45
+ .attach('file1', new Blob(['test'], { type: 'text/plain' }), 'file1.txt')
46
+
47
+ const init = mockedFetch.mock.calls[0][1]
48
+ expect(init).toBeDefined()
49
+
50
+ const headers = init?.headers as Headers
51
+
52
+ // FormData 需要自动删除 Content-Type
53
+ expect(headers.get('Content-Type')).toBeNull()
54
+
55
+ const body = init?.body as FormData
56
+
57
+ expect(body.getAll('name')).toEqual(['John'])
58
+ expect(body.getAll('friends')).toEqual(['Tom', 'Bob'])
59
+ expect(body.getAll('age')).toEqual(['12'])
60
+ expect((body.get('file1') as File).name).toBe('file1.txt')
61
+ })
62
+ })
63
+
64
+
65
+ test('send application/json request', async () => {
66
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
67
+
68
+ const requestBody = {
69
+ name: 'John',
70
+ friends: ['Tom', 'Bob'],
71
+ }
72
+ await request
73
+ .post('http://test.com')
74
+ .send(requestBody)
75
+
76
+ const init = mockedFetch.mock.calls[0][1]
77
+ expect(init).toBeDefined()
78
+
79
+ const headers = init?.headers as Headers
80
+ expect(headers.get('Content-Type')).toBe('application/json')
81
+
82
+ const body = init?.body as string
83
+ expect(body).toBe(JSON.stringify(requestBody))
84
+ })
85
+
86
+
87
+ test('send application/x-www-form-urlencoded request', async () => {
88
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
89
+
90
+ await request
91
+ .post('http://test.com')
92
+ .type('form')
93
+ .send({
94
+ name: 'John',
95
+ friends: ['Tom', 'Bob'],
96
+ })
97
+
98
+ const init = mockedFetch.mock.calls[0][1]
99
+ expect(init).toBeDefined()
100
+
101
+ const headers = init?.headers as Headers
102
+ expect(headers.get('Content-Type')).toBe('application/x-www-form-urlencoded')
103
+
104
+ const body = init?.body as URLSearchParams
105
+ expect(body).toBeInstanceOf(URLSearchParams)
106
+ expect(body.toString()).toBe('name=John&friends=Tom&friends=Bob')
107
+ })
@@ -0,0 +1,121 @@
1
+ import { expect, test } from '@jest/globals'
2
+ import { Mock } from 'jest-mock'
3
+ import { request } from './request.js'
4
+
5
+
6
+ test('send get request', async () => {
7
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
8
+
9
+ await request
10
+ .get('http://test.com')
11
+
12
+
13
+ expect(mockedFetch.mock.calls).toHaveLength(1)
14
+
15
+ const url = mockedFetch.mock.calls[0][0]
16
+ const init = mockedFetch.mock.calls[0][1]
17
+
18
+ expect(url).toBe('http://test.com/')
19
+ expect(init?.method).toBe('GET')
20
+ })
21
+
22
+
23
+ test('send post request', async () => {
24
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
25
+
26
+ await request
27
+ .post('http://test.com')
28
+
29
+ expect(mockedFetch.mock.calls).toHaveLength(1)
30
+
31
+ const url = mockedFetch.mock.calls[0][0]
32
+ const init = mockedFetch.mock.calls[0][1]
33
+
34
+ expect(url).toBe('http://test.com/')
35
+ expect(init?.method).toBe('POST')
36
+
37
+ expect(init?.headers).toBeInstanceOf(Headers)
38
+
39
+ const headers = init?.headers as Headers
40
+ expect(headers.get('content-type')).toBeNull()
41
+ })
42
+
43
+ test('send put request', async () => {
44
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
45
+
46
+ await request
47
+ .put('http://test.com')
48
+
49
+ expect(mockedFetch.mock.calls).toHaveLength(1)
50
+
51
+ const url = mockedFetch.mock.calls[0][0]
52
+ const init = mockedFetch.mock.calls[0][1]
53
+
54
+ expect(url).toBe('http://test.com/')
55
+ expect(init?.method).toBe('PUT')
56
+
57
+ expect(init?.headers).toBeInstanceOf(Headers)
58
+
59
+ const headers = init?.headers as Headers
60
+ expect(headers.get('content-type')).toBeNull()
61
+ })
62
+
63
+ test('send patch request', async () => {
64
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
65
+
66
+ await request
67
+ .patch('http://test.com')
68
+
69
+ expect(mockedFetch.mock.calls).toHaveLength(1)
70
+
71
+ const url = mockedFetch.mock.calls[0][0]
72
+ const init = mockedFetch.mock.calls[0][1]
73
+
74
+ expect(url).toBe('http://test.com/')
75
+ expect(init?.method).toBe('PATCH')
76
+
77
+ expect(init?.headers).toBeInstanceOf(Headers)
78
+
79
+ const headers = init?.headers as Headers
80
+ expect(headers.get('content-type')).toBeNull()
81
+ })
82
+
83
+ test('send head request', async () => {
84
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
85
+
86
+ await request
87
+ .head('http://test.com')
88
+
89
+ expect(mockedFetch.mock.calls).toHaveLength(1)
90
+
91
+ const url = mockedFetch.mock.calls[0][0]
92
+ const init = mockedFetch.mock.calls[0][1]
93
+
94
+ expect(url).toBe('http://test.com/')
95
+ expect(init?.method).toBe('HEAD')
96
+
97
+ expect(init?.headers).toBeInstanceOf(Headers)
98
+
99
+ const headers = init?.headers as Headers
100
+ expect(headers.get('content-type')).toBeNull()
101
+ })
102
+
103
+ test('send DELETE request', async () => {
104
+ const mockedFetch = global.fetch as Mock<typeof global.fetch>
105
+
106
+ await request
107
+ .del('http://test.com')
108
+
109
+ expect(mockedFetch.mock.calls).toHaveLength(1)
110
+
111
+ const url = mockedFetch.mock.calls[0][0]
112
+ const init = mockedFetch.mock.calls[0][1]
113
+
114
+ expect(url).toBe('http://test.com/')
115
+ expect(init?.method).toBe('DELETE')
116
+
117
+ expect(init?.headers).toBeInstanceOf(Headers)
118
+
119
+ const headers = init?.headers as Headers
120
+ expect(headers.get('content-type')).toBeNull()
121
+ })
@@ -0,0 +1,48 @@
1
+ import { expect, jest, test } from '@jest/globals'
2
+ import { request } from './request.js'
3
+
4
+
5
+ test('timeout request', async () => {
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ const mockedFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => new Promise((resolve, reject) => {
8
+ let finished = false
9
+
10
+ if (init?.signal) {
11
+ const signal = init.signal
12
+ signal.onabort = () => {
13
+ if (finished) return
14
+ finished = true
15
+ if (signal.reason) reject(signal.reason)
16
+ reject(new DOMException('AbortError', 'AbortError'))
17
+ }
18
+ }
19
+
20
+ // sleet 500ms
21
+ setTimeout(
22
+ () => {
23
+ if (finished) return
24
+ finished = true
25
+
26
+ resolve(new Response(
27
+ JSON.stringify({ code: '200' }),
28
+ {
29
+ headers: {
30
+ 'content-type': 'application/json',
31
+ },
32
+ }
33
+ ))
34
+ },
35
+ 500,
36
+ )
37
+ }))
38
+
39
+ try {
40
+ await request
41
+ .get('http://test.com')
42
+ .option('fetchAPI', mockedFetch)
43
+ .timeout(100)
44
+ } catch (e) {
45
+ expect(e).toBeInstanceOf(DOMException)
46
+ expect((e as DOMException).name).toBe('AbortError')
47
+ }
48
+ })
@@ -0,0 +1,32 @@
1
+ import { afterAll, beforeAll, beforeEach, jest } from '@jest/globals'
2
+ // import { TextEncoder, TextDecoder } from 'util'
3
+
4
+
5
+ // https://github.com/inrupt/solid-client-authn-js/issues/1676
6
+ // global.TextEncoder = TextEncoder
7
+ // global.TextDecoder = TextDecoder as any
8
+
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
+ const mockedFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => Promise.resolve(new Response(
12
+ JSON.stringify({ code: '200' }),
13
+ {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ }
18
+ )))
19
+
20
+ const unMockedFetch = global.fetch
21
+
22
+ beforeAll(() => {
23
+ global.fetch = mockedFetch as unknown as typeof fetch
24
+ })
25
+
26
+ afterAll(() => {
27
+ global.fetch = unMockedFetch
28
+ })
29
+
30
+ beforeEach(() => {
31
+ mockedFetch.mockClear()
32
+ })
@@ -0,0 +1,2 @@
1
+ /// <reference types="node" />
2
+ export declare function isBuffer(obj: any): obj is Buffer;
@@ -0,0 +1,4 @@
1
+ import { isBrowser } from './is-browser';
2
+ export function isBuffer(obj) {
3
+ return isBrowser() ? false : Buffer.isBuffer(obj);
4
+ }
@@ -5,11 +5,13 @@ import { isBlob } from './is/is-blob.js';
5
5
  import { isFile } from './is/is-file.js';
6
6
  import { isFormData } from './is/is-form-data.js';
7
7
  import { isHeaders } from './is/is-headers.js';
8
+ import { isBuffer } from './is/is-buffer.js';
8
9
  import { isUrlSearchParams } from './is/is-url-search-params.js';
9
10
  import { assignKeqRequestBody } from './util/assign-keq-request-body.js';
10
11
  import { base64Encode } from './util/base64.js';
11
12
  import { fixContentType } from './util/fix-content-type.js';
12
13
  import { getUniqueCodeIdentifier } from './util/get-unique-code-identifier.js';
14
+ import { isValidHeaderValue } from './util/is-valid-header-value.js';
13
15
  /**
14
16
  * @description Keq 扩展 API,人性化的常用的API
15
17
  */
@@ -34,10 +36,16 @@ export class Keq extends Core {
34
36
  });
35
37
  }
36
38
  else if (typeof headersOrName === 'string' && value) {
39
+ if (!isValidHeaderValue(value)) {
40
+ throw new Exception(`[Invalid header] Key: ${headersOrName} Value: ${value}`);
41
+ }
37
42
  this.requestContext.headers.set(headersOrName, value);
38
43
  }
39
44
  else if (typeof headersOrName === 'object') {
40
45
  for (const [key, value] of Object.entries(headersOrName)) {
46
+ if (!isValidHeaderValue(value)) {
47
+ throw new Exception(`[Invalid header] Key: ${key} Value: ${value}`);
48
+ }
41
49
  this.requestContext.headers.set(key, value);
42
50
  }
43
51
  }
@@ -144,7 +152,7 @@ export class Keq extends Core {
144
152
  else if (isFile(value)) {
145
153
  file = value;
146
154
  }
147
- else if (value instanceof Buffer) {
155
+ else if (isBuffer(value)) {
148
156
  const formData = new FormData();
149
157
  formData.set(key, new Blob([value]), arg3);
150
158
  file = formData.get(key);
@@ -2,7 +2,7 @@ import { URL } from 'whatwg-url';
2
2
  import { Exception } from '../exception/exception.js';
3
3
  import { compilePathnameTemplate } from '../util/compile-pathname-template.js';
4
4
  import { ABORT_PROPERTY } from '../constant.js';
5
- import { isBrowser } from '../is/is-browser.js';
5
+ import { isBuffer } from "../is/is-buffer.js";
6
6
  function compileUrl(obj, routeParams) {
7
7
  const url = new URL(typeof obj === 'string' ? obj : obj.href);
8
8
  try {
@@ -64,7 +64,7 @@ function compileBody(ctx) {
64
64
  request.headers.delete('content-type');
65
65
  return form;
66
66
  }
67
- if (!isBrowser() && body instanceof Buffer)
67
+ if (isBuffer(body))
68
68
  return body;
69
69
  if (body === undefined)
70
70
  return body;
@@ -1,3 +1,4 @@
1
+ import { isBuffer } from "../is/is-buffer.js";
1
2
  import { isBlob } from '../is/is-blob.js';
2
3
  import { isFile } from '../is/is-file.js';
3
4
  import { isFormData } from '../is/is-form-data.js';
@@ -24,7 +25,7 @@ export function cloneBody(obj) {
24
25
  if (isBlob(obj)) {
25
26
  return obj;
26
27
  }
27
- if (obj instanceof Buffer) {
28
+ if (isBuffer(obj)) {
28
29
  return obj;
29
30
  }
30
31
  if (obj === null) {
@@ -0,0 +1 @@
1
+ export declare function isValidHeaderValue(str: any): boolean;
@@ -0,0 +1,4 @@
1
+ export function isValidHeaderValue(str) {
2
+ const regex = /^[\t\x20-\x7E\x80-\xFF]*$/;
3
+ return regex.test(str);
4
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="node" />
2
+ export declare function isBuffer(obj: any): obj is Buffer;
@@ -0,0 +1,18 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports", "./is-browser"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isBuffer = void 0;
13
+ const is_browser_1 = require("./is-browser");
14
+ function isBuffer(obj) {
15
+ return (0, is_browser_1.isBrowser)() ? false : Buffer.isBuffer(obj);
16
+ }
17
+ exports.isBuffer = isBuffer;
18
+ });
@@ -4,7 +4,7 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "./core.js", "./exception/exception.js", "./exception/invalid-arguments.exception.js", "./is/is-blob.js", "./is/is-file.js", "./is/is-form-data.js", "./is/is-headers.js", "./is/is-url-search-params.js", "./util/assign-keq-request-body.js", "./util/base64.js", "./util/fix-content-type.js", "./util/get-unique-code-identifier.js"], factory);
7
+ define(["require", "exports", "./core.js", "./exception/exception.js", "./exception/invalid-arguments.exception.js", "./is/is-blob.js", "./is/is-file.js", "./is/is-form-data.js", "./is/is-headers.js", "./is/is-buffer.js", "./is/is-url-search-params.js", "./util/assign-keq-request-body.js", "./util/base64.js", "./util/fix-content-type.js", "./util/get-unique-code-identifier.js", "./util/is-valid-header-value.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
@@ -17,11 +17,13 @@
17
17
  const is_file_js_1 = require("./is/is-file.js");
18
18
  const is_form_data_js_1 = require("./is/is-form-data.js");
19
19
  const is_headers_js_1 = require("./is/is-headers.js");
20
+ const is_buffer_js_1 = require("./is/is-buffer.js");
20
21
  const is_url_search_params_js_1 = require("./is/is-url-search-params.js");
21
22
  const assign_keq_request_body_js_1 = require("./util/assign-keq-request-body.js");
22
23
  const base64_js_1 = require("./util/base64.js");
23
24
  const fix_content_type_js_1 = require("./util/fix-content-type.js");
24
25
  const get_unique_code_identifier_js_1 = require("./util/get-unique-code-identifier.js");
26
+ const is_valid_header_value_js_1 = require("./util/is-valid-header-value.js");
25
27
  /**
26
28
  * @description Keq 扩展 API,人性化的常用的API
27
29
  */
@@ -46,10 +48,16 @@
46
48
  });
47
49
  }
48
50
  else if (typeof headersOrName === 'string' && value) {
51
+ if (!(0, is_valid_header_value_js_1.isValidHeaderValue)(value)) {
52
+ throw new exception_js_1.Exception(`[Invalid header] Key: ${headersOrName} Value: ${value}`);
53
+ }
49
54
  this.requestContext.headers.set(headersOrName, value);
50
55
  }
51
56
  else if (typeof headersOrName === 'object') {
52
57
  for (const [key, value] of Object.entries(headersOrName)) {
58
+ if (!(0, is_valid_header_value_js_1.isValidHeaderValue)(value)) {
59
+ throw new exception_js_1.Exception(`[Invalid header] Key: ${key} Value: ${value}`);
60
+ }
53
61
  this.requestContext.headers.set(key, value);
54
62
  }
55
63
  }
@@ -156,7 +164,7 @@
156
164
  else if ((0, is_file_js_1.isFile)(value)) {
157
165
  file = value;
158
166
  }
159
- else if (value instanceof Buffer) {
167
+ else if ((0, is_buffer_js_1.isBuffer)(value)) {
160
168
  const formData = new FormData();
161
169
  formData.set(key, new Blob([value]), arg3);
162
170
  file = formData.get(key);
@@ -4,7 +4,7 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "whatwg-url", "../exception/exception.js", "../util/compile-pathname-template.js", "../constant.js", "../is/is-browser.js"], factory);
7
+ define(["require", "exports", "whatwg-url", "../exception/exception.js", "../util/compile-pathname-template.js", "../constant.js", "../is/is-buffer.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
@@ -14,7 +14,7 @@
14
14
  const exception_js_1 = require("../exception/exception.js");
15
15
  const compile_pathname_template_js_1 = require("../util/compile-pathname-template.js");
16
16
  const constant_js_1 = require("../constant.js");
17
- const is_browser_js_1 = require("../is/is-browser.js");
17
+ const is_buffer_js_1 = require("../is/is-buffer.js");
18
18
  function compileUrl(obj, routeParams) {
19
19
  const url = new whatwg_url_1.URL(typeof obj === 'string' ? obj : obj.href);
20
20
  try {
@@ -76,7 +76,7 @@
76
76
  request.headers.delete('content-type');
77
77
  return form;
78
78
  }
79
- if (!(0, is_browser_js_1.isBrowser)() && body instanceof Buffer)
79
+ if ((0, is_buffer_js_1.isBuffer)(body))
80
80
  return body;
81
81
  if (body === undefined)
82
82
  return body;
@@ -4,12 +4,13 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "../is/is-blob.js", "../is/is-file.js", "../is/is-form-data.js", "../is/is-object.js", "../is/is-url-search-params.js"], factory);
7
+ define(["require", "exports", "../is/is-buffer.js", "../is/is-blob.js", "../is/is-file.js", "../is/is-form-data.js", "../is/is-object.js", "../is/is-url-search-params.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.cloneBody = void 0;
13
+ const is_buffer_js_1 = require("../is/is-buffer.js");
13
14
  const is_blob_js_1 = require("../is/is-blob.js");
14
15
  const is_file_js_1 = require("../is/is-file.js");
15
16
  const is_form_data_js_1 = require("../is/is-form-data.js");
@@ -36,7 +37,7 @@
36
37
  if ((0, is_blob_js_1.isBlob)(obj)) {
37
38
  return obj;
38
39
  }
39
- if (obj instanceof Buffer) {
40
+ if ((0, is_buffer_js_1.isBuffer)(obj)) {
40
41
  return obj;
41
42
  }
42
43
  if (obj === null) {
@@ -0,0 +1 @@
1
+ export declare function isValidHeaderValue(str: any): boolean;
@@ -0,0 +1,18 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isValidHeaderValue = void 0;
13
+ function isValidHeaderValue(str) {
14
+ const regex = /^[\t\x20-\x7E\x80-\xFF]*$/;
15
+ return regex.test(str);
16
+ }
17
+ exports.isValidHeaderValue = isValidHeaderValue;
18
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keq",
3
- "version": "2.6.8",
3
+ "version": "2.6.10",
4
4
  "description": "Request API write by Typescript for flexibility, readability, and a low learning curve.",
5
5
  "keywords": [
6
6
  "request",
@@ -55,19 +55,20 @@
55
55
  "@rushstack/eslint-patch": "^1.10.3",
56
56
  "@types/clone": "^2.1.4",
57
57
  "@types/minimatch": "^5.1.2",
58
- "@types/node": "^20.12.12",
59
- "@typescript-eslint/eslint-plugin": "^7.10.0",
60
- "@typescript-eslint/parser": "^7.10.0",
58
+ "@types/node": "^20.14.1",
59
+ "@typescript-eslint/eslint-plugin": "^7.12.0",
60
+ "@typescript-eslint/parser": "^7.12.0",
61
61
  "eslint": "^8.57.0",
62
62
  "husky": "^9.0.11",
63
63
  "is-ci": "^3.0.1",
64
64
  "jest": "^29.7.0",
65
+ "jest-environment-jsdom": "^29.7.0",
65
66
  "jest-mock": "^29.7.0",
66
67
  "standard-version": "^9.5.0",
67
- "ts-jest": "^29.1.3",
68
+ "ts-jest": "^29.1.5",
68
69
  "ts-node": "^10.9.2",
69
- "ts-patch": "^3.1.2",
70
- "typescript": "^5.4.5",
70
+ "ts-patch": "^3.2.0",
71
+ "typescript": "5.4.5",
71
72
  "typescript-transform-paths": "^3.4.7"
72
73
  },
73
74
  "packageManager": "pnpm@9.1.4",