@squiz/dx-common-lib 1.2.11 → 1.2.13-alpha.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/CHANGELOG.md +8 -66
- package/lib/api-key-validation/ApiKeyValidationService.d.ts +4 -0
- package/lib/api-key-validation/ApiKeyValidationService.js +3 -0
- package/lib/api-key-validation/ApiKeyValidationService.js.map +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.d.ts +17 -0
- package/lib/api-key-validation/CloudflareApiKeyService.js +72 -0
- package/lib/api-key-validation/CloudflareApiKeyService.js.map +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.js +93 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.js.map +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.d.ts +5 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.js +13 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.js.map +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.js +17 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.js.map +1 -0
- package/lib/api-key-validation/getApiKeyService.d.ts +6 -0
- package/lib/api-key-validation/getApiKeyService.js +23 -0
- package/lib/api-key-validation/getApiKeyService.js.map +1 -0
- package/lib/api-key-validation/getApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/getApiKeyService.spec.js +24 -0
- package/lib/api-key-validation/getApiKeyService.spec.js.map +1 -0
- package/lib/assertions/assertAssignWithDefaultUndefinedValue.d.ts +0 -1
- package/lib/assertions/assertAssignWithDefaultUndefinedValue.js +1 -3
- package/lib/assertions/assertAssignWithDefaultUndefinedValue.js.map +1 -1
- package/lib/cache/applyDefaultRulesToCacheControlObject.d.ts +19 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.js +21 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.js.map +1 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.d.ts +1 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.js +97 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.js.map +1 -0
- package/lib/cache/cacheControlToString.d.ts +2 -0
- package/lib/cache/cacheControlToString.js +24 -0
- package/lib/cache/cacheControlToString.js.map +1 -0
- package/lib/cache/cacheControlToString.spec.d.ts +1 -0
- package/lib/cache/cacheControlToString.spec.js +34 -0
- package/lib/cache/cacheControlToString.spec.js.map +1 -0
- package/lib/cache/index.d.ts +4 -0
- package/lib/cache/index.js +21 -0
- package/lib/cache/index.js.map +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.d.ts +5 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.js +26 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.js.map +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.d.ts +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.js +21 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.js.map +1 -0
- package/lib/cache/parseCacheControl.d.ts +19 -0
- package/lib/cache/parseCacheControl.js +46 -0
- package/lib/cache/parseCacheControl.js.map +1 -0
- package/lib/cache/parseCacheControl.spec.d.ts +1 -0
- package/lib/cache/parseCacheControl.spec.js +70 -0
- package/lib/cache/parseCacheControl.spec.js.map +1 -0
- package/lib/error/InvalidTokenError.d.ts +5 -0
- package/lib/error/InvalidTokenError.js +12 -0
- package/lib/error/InvalidTokenError.js.map +1 -0
- package/lib/error/UnAuthenticatedRequestError.d.ts +4 -0
- package/lib/error/UnAuthenticatedRequestError.js +11 -0
- package/lib/error/UnAuthenticatedRequestError.js.map +1 -0
- package/lib/error/UnprivilegedError.d.ts +5 -0
- package/lib/error/UnprivilegedError.js +12 -0
- package/lib/error/UnprivilegedError.js.map +1 -0
- package/lib/error/index.d.ts +3 -0
- package/lib/error/index.js +3 -0
- package/lib/error/index.js.map +1 -1
- package/lib/formatted-text/formattedTextToHtmlSting.d.ts +4 -0
- package/lib/formatted-text/formattedTextToHtmlSting.js +85 -0
- package/lib/formatted-text/formattedTextToHtmlSting.js.map +1 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.d.ts +1 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.js +212 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/server-utils/apiKeyMiddleware.d.ts +3 -0
- package/lib/server-utils/apiKeyMiddleware.js +20 -0
- package/lib/server-utils/apiKeyMiddleware.js.map +1 -0
- package/lib/server-utils/apiKeyMiddleware.spec.d.ts +1 -0
- package/lib/server-utils/apiKeyMiddleware.spec.js +39 -0
- package/lib/server-utils/apiKeyMiddleware.spec.js.map +1 -0
- package/lib/util/getPageInfo.d.ts +2 -2
- package/package.json +10 -8
- package/src/api-key-validation/ApiKeyValidationService.ts +4 -0
- package/src/api-key-validation/CloudflareApiKeyService.spec.ts +122 -0
- package/src/api-key-validation/CloudflareApiKeyService.ts +96 -0
- package/src/api-key-validation/DevelopmentApiKeyService.spec.ts +17 -0
- package/src/api-key-validation/DevelopmentApiKeyService.ts +10 -0
- package/src/api-key-validation/getApiKeyService.spec.ts +32 -0
- package/src/api-key-validation/getApiKeyService.ts +27 -0
- package/src/assertions/assertAssignWithDefaultUndefinedValue.ts +0 -5
- package/src/cache/applyDefaultRulesToCacheControlObject.spec.ts +126 -0
- package/src/cache/applyDefaultRulesToCacheControlObject.ts +23 -0
- package/src/cache/cacheControlToString.spec.ts +43 -0
- package/src/cache/cacheControlToString.ts +22 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/parseAndSanitiseCacheControlHeader.spec.ts +25 -0
- package/src/cache/parseAndSanitiseCacheControlHeader.ts +28 -0
- package/src/cache/parseCacheControl.spec.ts +89 -0
- package/src/cache/parseCacheControl.ts +74 -0
- package/src/error/InvalidTokenError.ts +8 -0
- package/src/error/UnAuthenticatedRequestError.ts +7 -0
- package/src/error/UnprivilegedError.ts +8 -0
- package/src/error/index.ts +4 -0
- package/src/formatted-text/formattedTextToHtmlSting.spec.ts +235 -0
- package/src/formatted-text/formattedTextToHtmlSting.ts +100 -0
- package/src/index.ts +7 -0
- package/src/server-utils/apiKeyMiddleware.spec.ts +50 -0
- package/src/server-utils/apiKeyMiddleware.ts +23 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,32 @@
|
|
1
|
+
import { CloudflareApiKeyService } from './CloudflareApiKeyService';
|
2
|
+
import { DevelopmentApiKeyService } from './DevelopmentApiKeyService';
|
3
|
+
import { getApiKeyService } from './getApiKeyService';
|
4
|
+
|
5
|
+
describe('getApiKeyService', () => {
|
6
|
+
it('should return an instance of DevelopmentApiKeyService when NODE_ENV is development', () => {
|
7
|
+
process.env.NODE_ENV = 'development';
|
8
|
+
expect(getApiKeyService()).toBeInstanceOf(DevelopmentApiKeyService);
|
9
|
+
});
|
10
|
+
|
11
|
+
it('should return an instance of CloudflareApiKeyService when NODE_ENV is production', () => {
|
12
|
+
process.env.NODE_ENV = 'production';
|
13
|
+
expect(getApiKeyService({ deploymentEnvironment: 'any', squizRegion: 'value' })).toBeInstanceOf(
|
14
|
+
CloudflareApiKeyService,
|
15
|
+
);
|
16
|
+
});
|
17
|
+
|
18
|
+
it('should throw an error if no prodEnv values available', () => {
|
19
|
+
process.env.NODE_ENV = 'production';
|
20
|
+
expect(() => getApiKeyService()).toThrow(
|
21
|
+
new Error(`invalid production state, need to have deploymentEnvironment and squiz region set`),
|
22
|
+
);
|
23
|
+
});
|
24
|
+
|
25
|
+
it('should throw an error if NODE_ENV is anything other than development or production', () => {
|
26
|
+
process.env.NODE_ENV = 'something invalid';
|
27
|
+
|
28
|
+
expect(() => getApiKeyService()).toThrow(
|
29
|
+
new Error(`NODE_ENV env var can only be values "production" or "development"`),
|
30
|
+
);
|
31
|
+
});
|
32
|
+
});
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { getNodeEnv, never } from '../util';
|
2
|
+
import { CloudflareApiKeyService } from './CloudflareApiKeyService';
|
3
|
+
import { DevelopmentApiKeyService } from './DevelopmentApiKeyService';
|
4
|
+
|
5
|
+
export function getApiKeyService(prodEnv?: {
|
6
|
+
deploymentEnvironment: string;
|
7
|
+
squizRegion: string;
|
8
|
+
}): DevelopmentApiKeyService | CloudflareApiKeyService {
|
9
|
+
const env = getNodeEnv();
|
10
|
+
|
11
|
+
switch (env) {
|
12
|
+
// this the DX team dev environment
|
13
|
+
case 'development':
|
14
|
+
return new DevelopmentApiKeyService();
|
15
|
+
case 'production':
|
16
|
+
if (prodEnv == undefined) {
|
17
|
+
throw new Error(`invalid production state, need to have deploymentEnvironment and squiz region set`);
|
18
|
+
}
|
19
|
+
|
20
|
+
return new CloudflareApiKeyService(
|
21
|
+
`dx-${prodEnv.deploymentEnvironment}-${prodEnv.squizRegion}-cmp-cloudflare-keys`,
|
22
|
+
);
|
23
|
+
|
24
|
+
default:
|
25
|
+
never(env);
|
26
|
+
}
|
27
|
+
}
|
@@ -15,8 +15,3 @@ export function assertAssignWithDefaultUndefinedValue<T>(
|
|
15
15
|
|
16
16
|
return assertAssign(value, assertionFunc, errorMessage);
|
17
17
|
}
|
18
|
-
|
19
|
-
export const isSomeEnum =
|
20
|
-
<TEnum>(enumType: TEnum) =>
|
21
|
-
(token: unknown): token is TEnum[keyof TEnum] =>
|
22
|
-
Object.values(enumType).includes(token as TEnum[keyof TEnum]);
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import { applyDefaultRulesToCacheControlObject } from './applyDefaultRulesToCacheControlObject';
|
2
|
+
|
3
|
+
describe('applyDefaultRulesToCacheControlObject', () => {
|
4
|
+
const MIN_CACHE_SECONDS = 30;
|
5
|
+
const MAX_CACHE_SECONDS = 1800;
|
6
|
+
|
7
|
+
it('should return a Cache-Control object with the max-age directive clamped to the minimum allowed value if the value is 0', () => {
|
8
|
+
expect(
|
9
|
+
applyDefaultRulesToCacheControlObject({
|
10
|
+
'max-age': 0,
|
11
|
+
}),
|
12
|
+
).toEqual({
|
13
|
+
'max-age': MIN_CACHE_SECONDS,
|
14
|
+
});
|
15
|
+
});
|
16
|
+
it('should return a Cache-Control object with the max-age directive clamped to the minimum allowed value if it is less than the minimum', () => {
|
17
|
+
expect(
|
18
|
+
applyDefaultRulesToCacheControlObject({
|
19
|
+
'max-age': MIN_CACHE_SECONDS - 1,
|
20
|
+
}),
|
21
|
+
).toEqual({
|
22
|
+
'max-age': MIN_CACHE_SECONDS,
|
23
|
+
});
|
24
|
+
});
|
25
|
+
|
26
|
+
it('should return a Cache-Control object with the max-age directive clamped to the maximum allowed value if it is greater than the maximum', () => {
|
27
|
+
expect(
|
28
|
+
applyDefaultRulesToCacheControlObject({
|
29
|
+
'max-age': MAX_CACHE_SECONDS + 1,
|
30
|
+
}),
|
31
|
+
).toEqual({
|
32
|
+
'max-age': MAX_CACHE_SECONDS,
|
33
|
+
});
|
34
|
+
});
|
35
|
+
|
36
|
+
it('should return a Cache-Control object with the s-maxage directive clamped to the minimum allowed value if the value is 0', () => {
|
37
|
+
expect(
|
38
|
+
applyDefaultRulesToCacheControlObject({
|
39
|
+
's-maxage': 0,
|
40
|
+
}),
|
41
|
+
).toEqual({
|
42
|
+
's-maxage': MIN_CACHE_SECONDS,
|
43
|
+
});
|
44
|
+
});
|
45
|
+
|
46
|
+
it('should return a Cache-Control object with the s-maxage directive clamped to the minimum allowed value if it is less than the minimum', () => {
|
47
|
+
expect(
|
48
|
+
applyDefaultRulesToCacheControlObject({
|
49
|
+
's-maxage': MIN_CACHE_SECONDS - 1,
|
50
|
+
}),
|
51
|
+
).toEqual({
|
52
|
+
's-maxage': MIN_CACHE_SECONDS,
|
53
|
+
});
|
54
|
+
});
|
55
|
+
|
56
|
+
it('should return a Cache-Control object with the s-maxage directive clamped to the maximum allowed value if it is greater than the maximum', () => {
|
57
|
+
expect(
|
58
|
+
applyDefaultRulesToCacheControlObject({
|
59
|
+
's-maxage': MAX_CACHE_SECONDS + 1,
|
60
|
+
}),
|
61
|
+
).toEqual({
|
62
|
+
's-maxage': MAX_CACHE_SECONDS,
|
63
|
+
});
|
64
|
+
});
|
65
|
+
|
66
|
+
it('should return a Cache-Control object with the min-fresh directive clamped to the minimum allowed value if the value is 0', () => {
|
67
|
+
expect(
|
68
|
+
applyDefaultRulesToCacheControlObject({
|
69
|
+
'min-fresh': 0,
|
70
|
+
}),
|
71
|
+
).toEqual({
|
72
|
+
'min-fresh': MIN_CACHE_SECONDS,
|
73
|
+
});
|
74
|
+
});
|
75
|
+
it('should return a Cache-Control object with the min-fresh directive clamped to the maximum allowed value if it is greater than the maximum', () => {
|
76
|
+
expect(
|
77
|
+
applyDefaultRulesToCacheControlObject({
|
78
|
+
'min-fresh': MAX_CACHE_SECONDS + 1,
|
79
|
+
}),
|
80
|
+
).toEqual({
|
81
|
+
'min-fresh': MAX_CACHE_SECONDS,
|
82
|
+
});
|
83
|
+
});
|
84
|
+
|
85
|
+
it('should remove the no-store directive from the input Cache-Control object', () => {
|
86
|
+
expect(
|
87
|
+
applyDefaultRulesToCacheControlObject({
|
88
|
+
'max-age': MAX_CACHE_SECONDS,
|
89
|
+
'no-store': true,
|
90
|
+
}),
|
91
|
+
).toEqual({
|
92
|
+
'max-age': MAX_CACHE_SECONDS,
|
93
|
+
});
|
94
|
+
});
|
95
|
+
|
96
|
+
it('should remove the no-cache directive from the input Cache-Control object', () => {
|
97
|
+
expect(
|
98
|
+
applyDefaultRulesToCacheControlObject({
|
99
|
+
'max-age': MAX_CACHE_SECONDS,
|
100
|
+
'no-cache': true,
|
101
|
+
}),
|
102
|
+
).toEqual({
|
103
|
+
'max-age': MAX_CACHE_SECONDS,
|
104
|
+
});
|
105
|
+
});
|
106
|
+
|
107
|
+
it('should return a Cache-Control object with all other directives unchanged from the input Cache-Control object', () => {
|
108
|
+
expect(
|
109
|
+
applyDefaultRulesToCacheControlObject({
|
110
|
+
'max-age': MAX_CACHE_SECONDS,
|
111
|
+
's-maxage': MAX_CACHE_SECONDS,
|
112
|
+
'min-fresh': MAX_CACHE_SECONDS,
|
113
|
+
'stale-while-revalidate': MAX_CACHE_SECONDS,
|
114
|
+
'stale-if-error': MAX_CACHE_SECONDS,
|
115
|
+
extension: 'foo',
|
116
|
+
}),
|
117
|
+
).toEqual({
|
118
|
+
'max-age': MAX_CACHE_SECONDS,
|
119
|
+
's-maxage': MAX_CACHE_SECONDS,
|
120
|
+
'min-fresh': MAX_CACHE_SECONDS,
|
121
|
+
'stale-while-revalidate': MAX_CACHE_SECONDS,
|
122
|
+
'stale-if-error': MAX_CACHE_SECONDS,
|
123
|
+
extension: 'foo',
|
124
|
+
});
|
125
|
+
});
|
126
|
+
});
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { CacheControl } from './parseCacheControl';
|
2
|
+
import { ensureBetween, MIN_CACHE_SECONDS, MAX_CACHE_SECONDS } from './parseAndSanitiseCacheControlHeader';
|
3
|
+
|
4
|
+
export function applyDefaultRulesToCacheControlObject(cacheControlInput: CacheControl) {
|
5
|
+
const cacheControl = { ...cacheControlInput };
|
6
|
+
|
7
|
+
if (cacheControl['max-age'] !== undefined) {
|
8
|
+
cacheControl['max-age'] = ensureBetween(cacheControl['max-age'], MIN_CACHE_SECONDS, MAX_CACHE_SECONDS);
|
9
|
+
}
|
10
|
+
|
11
|
+
if (cacheControl['s-maxage'] !== undefined) {
|
12
|
+
cacheControl['s-maxage'] = ensureBetween(cacheControl['s-maxage'], MIN_CACHE_SECONDS, MAX_CACHE_SECONDS);
|
13
|
+
}
|
14
|
+
|
15
|
+
if (cacheControl['min-fresh'] !== undefined) {
|
16
|
+
cacheControl['min-fresh'] = ensureBetween(cacheControl['min-fresh'], MIN_CACHE_SECONDS, MAX_CACHE_SECONDS);
|
17
|
+
}
|
18
|
+
|
19
|
+
delete cacheControl['no-store'];
|
20
|
+
delete cacheControl['no-cache'];
|
21
|
+
|
22
|
+
return cacheControl;
|
23
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { cacheControlToString } from './cacheControlToString';
|
2
|
+
|
3
|
+
describe('cacheControlToString', () => {
|
4
|
+
it('should return an empty string if the input Cache-Control object is empty', () => {
|
5
|
+
expect(cacheControlToString({})).toEqual('');
|
6
|
+
});
|
7
|
+
|
8
|
+
it('should return a Cache-Control header string with a single boolean directive', () => {
|
9
|
+
expect(cacheControlToString({ public: true })).toEqual('public');
|
10
|
+
});
|
11
|
+
|
12
|
+
it('should return a Cache-Control header string with a single numeric directive', () => {
|
13
|
+
expect(cacheControlToString({ 'max-age': 3600 })).toEqual('max-age=3600');
|
14
|
+
});
|
15
|
+
|
16
|
+
it('should return a Cache-Control header string with a single string directive', () => {
|
17
|
+
expect(cacheControlToString({ extension: 'foo' })).toEqual('extension="foo"');
|
18
|
+
});
|
19
|
+
|
20
|
+
it('should return a Cache-Control header string with multiple directives', () => {
|
21
|
+
expect(
|
22
|
+
cacheControlToString({
|
23
|
+
public: true,
|
24
|
+
'max-age': 3600,
|
25
|
+
's-maxage': 86400,
|
26
|
+
'min-fresh': 600,
|
27
|
+
'stale-while-revalidate': 60,
|
28
|
+
'stale-if-error': 86400,
|
29
|
+
extension: 'foo',
|
30
|
+
}),
|
31
|
+
).toEqual(
|
32
|
+
'public, max-age=3600, s-maxage=86400, min-fresh=600, stale-while-revalidate=60, stale-if-error=86400, extension="foo"',
|
33
|
+
);
|
34
|
+
});
|
35
|
+
|
36
|
+
it('should handle commas in string values in the input Cache-Control object', () => {
|
37
|
+
expect(
|
38
|
+
cacheControlToString({
|
39
|
+
extension: 'foo, bar',
|
40
|
+
}),
|
41
|
+
).toEqual('extension="foo, bar"');
|
42
|
+
});
|
43
|
+
});
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { CacheControl } from './parseCacheControl';
|
2
|
+
|
3
|
+
export function cacheControlToString(cacheControl: CacheControl): string {
|
4
|
+
const parts: string[] = [];
|
5
|
+
for (const key of Object.keys(cacheControl)) {
|
6
|
+
let value = cacheControl[key];
|
7
|
+
|
8
|
+
if (typeof value === 'boolean' && value === true) {
|
9
|
+
parts.push(key);
|
10
|
+
} else {
|
11
|
+
if (typeof value === 'number') {
|
12
|
+
value = value.toString();
|
13
|
+
} else if (typeof value === 'string') {
|
14
|
+
value = `"${value}"`;
|
15
|
+
}
|
16
|
+
|
17
|
+
parts.push(`${key}=${value}`);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
return parts.join(', ');
|
22
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { parseAndSanitiseCacheControlHeader } from './parseAndSanitiseCacheControlHeader';
|
2
|
+
|
3
|
+
describe('parseAndSanitiseCacheControlHeader', () => {
|
4
|
+
it('should return a default Cache-Control header string if the input is undefined', () => {
|
5
|
+
expect(parseAndSanitiseCacheControlHeader(undefined)).toEqual('public, max-age=1800');
|
6
|
+
});
|
7
|
+
|
8
|
+
it('should return a sanitized Cache-Control header string if the input is a valid Cache-Control header string', () => {
|
9
|
+
expect(parseAndSanitiseCacheControlHeader('max-age=3600, s-maxage=86400, min-fresh=600, no-store')).toEqual(
|
10
|
+
'max-age=1800, s-maxage=1800, min-fresh=600',
|
11
|
+
);
|
12
|
+
});
|
13
|
+
|
14
|
+
it('should remove the no-store directive from the input Cache-Control header string', () => {
|
15
|
+
expect(parseAndSanitiseCacheControlHeader('max-age=3600, no-store')).toEqual('max-age=1800');
|
16
|
+
});
|
17
|
+
|
18
|
+
it('should remove the no-cache directive from the input Cache-Control header string', () => {
|
19
|
+
expect(parseAndSanitiseCacheControlHeader('max-age=3600, no-cache')).toEqual('max-age=1800');
|
20
|
+
});
|
21
|
+
|
22
|
+
it('should remove both the no-store and no-cache directives from the input Cache-Control header string', () => {
|
23
|
+
expect(parseAndSanitiseCacheControlHeader('max-age=3600, no-store, no-cache')).toEqual('max-age=1800');
|
24
|
+
});
|
25
|
+
});
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { applyDefaultRulesToCacheControlObject } from './applyDefaultRulesToCacheControlObject';
|
2
|
+
import { cacheControlToString } from './cacheControlToString';
|
3
|
+
import { parseCacheControl } from './parseCacheControl';
|
4
|
+
|
5
|
+
export const MIN_CACHE_SECONDS = 30;
|
6
|
+
export const MAX_CACHE_SECONDS = minutesToSeconds(30);
|
7
|
+
|
8
|
+
export const DEFAULT_CACHE_CONTROL_HEADER = cacheControlToString({ public: true, 'max-age': minutesToSeconds(30) });
|
9
|
+
|
10
|
+
export function parseAndSanitiseCacheControlHeader(cacheControlString: string | undefined): string {
|
11
|
+
if (!cacheControlString) {
|
12
|
+
return DEFAULT_CACHE_CONTROL_HEADER;
|
13
|
+
}
|
14
|
+
|
15
|
+
let cacheControl = parseCacheControl(cacheControlString);
|
16
|
+
|
17
|
+
cacheControl = applyDefaultRulesToCacheControlObject(cacheControl);
|
18
|
+
|
19
|
+
return cacheControlToString(cacheControl);
|
20
|
+
}
|
21
|
+
|
22
|
+
export function ensureBetween(input: number, min: number, max: number): number {
|
23
|
+
return Math.max(min, Math.min(input, max));
|
24
|
+
}
|
25
|
+
|
26
|
+
function minutesToSeconds(minutes: number): number {
|
27
|
+
return minutes * 60;
|
28
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import { parseCacheControl } from './parseCacheControl';
|
2
|
+
|
3
|
+
describe('parseCacheControl', () => {
|
4
|
+
test('no-cache', () => {
|
5
|
+
const header = 'no-cache';
|
6
|
+
|
7
|
+
expect(parseCacheControl(header)).toEqual({ 'no-cache': true });
|
8
|
+
});
|
9
|
+
|
10
|
+
test('public', () => {
|
11
|
+
const header = 'public, max-age=3600';
|
12
|
+
|
13
|
+
expect(parseCacheControl(header)).toEqual({ public: true, 'max-age': 3600 });
|
14
|
+
});
|
15
|
+
|
16
|
+
test('private', () => {
|
17
|
+
const header = 'private, no-transform, max-age=3600';
|
18
|
+
|
19
|
+
expect(parseCacheControl(header)).toEqual({
|
20
|
+
private: true,
|
21
|
+
'no-transform': true,
|
22
|
+
'max-age': 3600,
|
23
|
+
});
|
24
|
+
});
|
25
|
+
|
26
|
+
test('max-age', () => {
|
27
|
+
const header = 'max-age=3600, public';
|
28
|
+
|
29
|
+
expect(parseCacheControl(header)).toEqual({ 'max-age': 3600, public: true });
|
30
|
+
});
|
31
|
+
|
32
|
+
test('s-maxage', () => {
|
33
|
+
const header = 's-maxage=604800, must-revalidate';
|
34
|
+
|
35
|
+
expect(parseCacheControl(header)).toEqual({
|
36
|
+
's-maxage': 604800,
|
37
|
+
'must-revalidate': true,
|
38
|
+
});
|
39
|
+
});
|
40
|
+
|
41
|
+
test('must-revalidate', () => {
|
42
|
+
const header = 'no-store, no-cache, must-revalidate';
|
43
|
+
|
44
|
+
expect(parseCacheControl(header)).toEqual({
|
45
|
+
'no-store': true,
|
46
|
+
'no-cache': true,
|
47
|
+
'must-revalidate': true,
|
48
|
+
});
|
49
|
+
});
|
50
|
+
|
51
|
+
test('no-store', () => {
|
52
|
+
const header = 'no-store';
|
53
|
+
|
54
|
+
expect(parseCacheControl(header)).toEqual({ 'no-store': true });
|
55
|
+
});
|
56
|
+
|
57
|
+
test('no-transform', () => {
|
58
|
+
const header = 'no-transform';
|
59
|
+
|
60
|
+
expect(parseCacheControl(header)).toEqual({ 'no-transform': true });
|
61
|
+
});
|
62
|
+
|
63
|
+
test('quoted string', () => {
|
64
|
+
const header = 'extension="foo, bar", no-transform';
|
65
|
+
|
66
|
+
expect(parseCacheControl(header)).toEqual({
|
67
|
+
extension: 'foo, bar',
|
68
|
+
'no-transform': true,
|
69
|
+
});
|
70
|
+
});
|
71
|
+
|
72
|
+
test('multiple values', () => {
|
73
|
+
const header = 'extension="foo, bar", max-age=3600, s-maxage=604800, no-transform, public';
|
74
|
+
|
75
|
+
expect(parseCacheControl(header)).toEqual({
|
76
|
+
extension: 'foo, bar',
|
77
|
+
'max-age': 3600,
|
78
|
+
's-maxage': 604800,
|
79
|
+
'no-transform': true,
|
80
|
+
public: true,
|
81
|
+
});
|
82
|
+
});
|
83
|
+
|
84
|
+
test('empty value', () => {
|
85
|
+
const header = 'extension="", max-age=3600';
|
86
|
+
|
87
|
+
expect(parseCacheControl(header)).toEqual({ extension: '', 'max-age': 3600 });
|
88
|
+
});
|
89
|
+
});
|
@@ -0,0 +1,74 @@
|
|
1
|
+
// please read https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
2
|
+
export interface CacheControl {
|
3
|
+
'max-age'?: number;
|
4
|
+
's-maxage'?: number;
|
5
|
+
'min-fresh'?: number;
|
6
|
+
'stale-while-revalidate'?: number;
|
7
|
+
'stale-if-error'?: number;
|
8
|
+
|
9
|
+
public?: true;
|
10
|
+
private?: true;
|
11
|
+
'no-store'?: true;
|
12
|
+
'no-cache'?: true;
|
13
|
+
'no-transform'?: true;
|
14
|
+
'must-revalidate'?: true;
|
15
|
+
'proxy-revalidate'?: true;
|
16
|
+
'must-understand'?: true;
|
17
|
+
immutable?: true;
|
18
|
+
extension?: string;
|
19
|
+
|
20
|
+
[key: string]: string | boolean | undefined | number;
|
21
|
+
}
|
22
|
+
|
23
|
+
export function parseCacheControl(header: string): CacheControl {
|
24
|
+
const parts = header.split(/,(?=(?:[^"]|"[^"]*")*$)/);
|
25
|
+
const result: CacheControl = {};
|
26
|
+
|
27
|
+
for (const part of parts) {
|
28
|
+
const [key, value] = part.split('=');
|
29
|
+
const trimmedKey = key.trim();
|
30
|
+
|
31
|
+
const trimmedValue = value !== undefined ? value.trim() : undefined;
|
32
|
+
const strippedValue = trimQuotes(trimmedValue);
|
33
|
+
const parsedValue = parseIntOrString(strippedValue, trimmedKey);
|
34
|
+
|
35
|
+
result[trimmedKey] = parsedValue;
|
36
|
+
}
|
37
|
+
|
38
|
+
return result;
|
39
|
+
}
|
40
|
+
|
41
|
+
function trimQuotes(str: string | undefined): string | undefined {
|
42
|
+
if (str === undefined) {
|
43
|
+
return str;
|
44
|
+
}
|
45
|
+
|
46
|
+
if (str[0] === '"' && str[str.length - 1] === '"') {
|
47
|
+
return str.substring(1, str.length - 1);
|
48
|
+
}
|
49
|
+
|
50
|
+
return str;
|
51
|
+
}
|
52
|
+
|
53
|
+
function parseIntOrString(str: string | undefined, key: string): string | number | true | undefined {
|
54
|
+
if (
|
55
|
+
key === 'max-age' ||
|
56
|
+
key === 's-maxage' ||
|
57
|
+
key === 'min-fresh' ||
|
58
|
+
key === 'stale-while-revalidate' ||
|
59
|
+
key === 'stale-if-error'
|
60
|
+
) {
|
61
|
+
if (str) {
|
62
|
+
const parsedValue = parseInt(str, 10);
|
63
|
+
return isNaN(parsedValue) ? str || true : parsedValue;
|
64
|
+
} else {
|
65
|
+
return 0;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
if (str === undefined) {
|
70
|
+
return true;
|
71
|
+
}
|
72
|
+
|
73
|
+
return str;
|
74
|
+
}
|
package/src/error/index.ts
CHANGED
@@ -4,3 +4,7 @@ export * from './InternalServerError';
|
|
4
4
|
export * from './ResourceNotFoundError';
|
5
5
|
export * from './TimeoutError';
|
6
6
|
export * from './MethodNotImplementedError';
|
7
|
+
|
8
|
+
export * from './InvalidTokenError';
|
9
|
+
export * from './UnprivilegedError';
|
10
|
+
export * from './UnAuthenticatedRequestError';
|