@suveren/gateway 0.9.0 → 0.10.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 +33 -1
- package/bin/suveren-gateway.js +203 -5
- package/content/integrations/erp.json +184 -0
- package/content/integrations/index.json +2 -1
- package/dist/control-plane/index.mjs +653 -179
- package/dist/mcp-server/http.mjs +466 -259
- package/dist/ui/assets/{index-CIiOCgjJ.js → index-B1qNl7zo.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/lib/as-pairing.mjs +77 -0
- package/lib/config.mjs +48 -0
- package/node_modules/undici/LICENSE +21 -0
- package/node_modules/undici/README.md +741 -0
- package/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/node_modules/undici/docs/docs/api/Client.md +288 -0
- package/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/node_modules/undici/docs/docs/api/Cookies.md +128 -0
- package/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
- package/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
- package/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/node_modules/undici/index-fetch.js +65 -0
- package/node_modules/undici/index.d.ts +3 -0
- package/node_modules/undici/index.js +234 -0
- package/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/node_modules/undici/lib/api/api-connect.js +110 -0
- package/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/node_modules/undici/lib/api/api-request.js +214 -0
- package/node_modules/undici/lib/api/api-stream.js +209 -0
- package/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/node_modules/undici/lib/api/index.js +7 -0
- package/node_modules/undici/lib/api/readable.js +580 -0
- package/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
- package/node_modules/undici/lib/core/connect.js +153 -0
- package/node_modules/undici/lib/core/constants.js +143 -0
- package/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/node_modules/undici/lib/core/errors.js +477 -0
- package/node_modules/undici/lib/core/request.js +485 -0
- package/node_modules/undici/lib/core/socks5-client.js +422 -0
- package/node_modules/undici/lib/core/socks5-utils.js +212 -0
- package/node_modules/undici/lib/core/symbols.js +75 -0
- package/node_modules/undici/lib/core/tree.js +160 -0
- package/node_modules/undici/lib/core/util.js +992 -0
- package/node_modules/undici/lib/dispatcher/agent.js +158 -0
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
- package/node_modules/undici/lib/dispatcher/client-h1.js +1746 -0
- package/node_modules/undici/lib/dispatcher/client-h2.js +1060 -0
- package/node_modules/undici/lib/dispatcher/client.js +668 -0
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +184 -0
- package/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
- package/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
- package/node_modules/undici/lib/encoding/index.js +33 -0
- package/node_modules/undici/lib/global.js +59 -0
- package/node_modules/undici/lib/handler/cache-handler.js +776 -0
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
- package/node_modules/undici/lib/handler/redirect-handler.js +238 -0
- package/node_modules/undici/lib/handler/retry-handler.js +504 -0
- package/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
- package/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/node_modules/undici/lib/interceptor/cache.js +621 -0
- package/node_modules/undici/lib/interceptor/decompress.js +551 -0
- package/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/node_modules/undici/lib/interceptor/dump.js +99 -0
- package/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/node_modules/undici/lib/util/cache.js +676 -0
- package/node_modules/undici/lib/util/date.js +670 -0
- package/node_modules/undici/lib/util/promise.js +28 -0
- package/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/node_modules/undici/lib/util/stats.js +32 -0
- package/node_modules/undici/lib/util/timers.js +425 -0
- package/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/node_modules/undici/lib/web/cache/util.js +45 -0
- package/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/node_modules/undici/lib/web/cookies/index.js +199 -0
- package/node_modules/undici/lib/web/cookies/parse.js +314 -0
- package/node_modules/undici/lib/web/cookies/util.js +352 -0
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +494 -0
- package/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
- package/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
- package/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/node_modules/undici/lib/web/fetch/index.js +2413 -0
- package/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/node_modules/undici/lib/web/fetch/util.js +1522 -0
- package/node_modules/undici/lib/web/infra/index.js +229 -0
- package/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/node_modules/undici/lib/web/websocket/connection.js +339 -0
- package/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
- package/node_modules/undici/lib/web/websocket/receiver.js +507 -0
- package/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
- package/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/node_modules/undici/lib/web/websocket/websocket.js +758 -0
- package/node_modules/undici/package.json +152 -0
- package/node_modules/undici/scripts/strip-comments.js +10 -0
- package/node_modules/undici/types/README.md +6 -0
- package/node_modules/undici/types/agent.d.ts +32 -0
- package/node_modules/undici/types/api.d.ts +43 -0
- package/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/node_modules/undici/types/cache.d.ts +36 -0
- package/node_modules/undici/types/client-stats.d.ts +15 -0
- package/node_modules/undici/types/client.d.ts +139 -0
- package/node_modules/undici/types/connector.d.ts +36 -0
- package/node_modules/undici/types/content-type.d.ts +21 -0
- package/node_modules/undici/types/cookies.d.ts +30 -0
- package/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/node_modules/undici/types/dispatcher.d.ts +275 -0
- package/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/node_modules/undici/types/errors.d.ts +177 -0
- package/node_modules/undici/types/eventsource.d.ts +66 -0
- package/node_modules/undici/types/fetch.d.ts +231 -0
- package/node_modules/undici/types/formdata.d.ts +114 -0
- package/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/node_modules/undici/types/global-origin.d.ts +7 -0
- package/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/node_modules/undici/types/handlers.d.ts +14 -0
- package/node_modules/undici/types/header.d.ts +160 -0
- package/node_modules/undici/types/index.d.ts +91 -0
- package/node_modules/undici/types/interceptors.d.ts +82 -0
- package/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/node_modules/undici/types/mock-client.d.ts +27 -0
- package/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/node_modules/undici/types/patch.d.ts +29 -0
- package/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/node_modules/undici/types/pool.d.ts +41 -0
- package/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/node_modules/undici/types/readable.d.ts +68 -0
- package/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/node_modules/undici/types/util.d.ts +18 -0
- package/node_modules/undici/types/utility.d.ts +7 -0
- package/node_modules/undici/types/webidl.d.ts +347 -0
- package/node_modules/undici/types/websocket.d.ts +188 -0
- package/package.json +4 -2
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const util = require('../core/util')
|
|
4
|
+
const {
|
|
5
|
+
parseCacheControlHeader,
|
|
6
|
+
hasInvalidCacheControlDirective,
|
|
7
|
+
parseVaryHeader,
|
|
8
|
+
hasVaryStar,
|
|
9
|
+
isInvalidOrWildcardVaryHeader,
|
|
10
|
+
isEtagUsable
|
|
11
|
+
} = require('../util/cache')
|
|
12
|
+
const { parseHttpDate } = require('../util/date.js')
|
|
13
|
+
|
|
14
|
+
function noop () {}
|
|
15
|
+
|
|
16
|
+
// Status codes that we can use some heuristics on to cache
|
|
17
|
+
const HEURISTICALLY_CACHEABLE_STATUS_CODES = [
|
|
18
|
+
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
// Status codes which semantic is not handled by the cache
|
|
22
|
+
// https://datatracker.ietf.org/doc/html/rfc9111#section-3
|
|
23
|
+
// This list should not grow beyond 206 unless the RFC is updated
|
|
24
|
+
// by a newer one including more. Please introduce another list if
|
|
25
|
+
// implementing caching of responses with the 'must-understand' directive.
|
|
26
|
+
const NOT_UNDERSTOOD_STATUS_CODES = [
|
|
27
|
+
206
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const MAX_RESPONSE_AGE = 2147483647000
|
|
31
|
+
|
|
32
|
+
function trimOWS (value) {
|
|
33
|
+
return value.replace(/^[\t ]+|[\t ]+$/g, '')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function arrayIncludes (array, value) {
|
|
37
|
+
for (let i = 0; i < array.length; i++) {
|
|
38
|
+
if (array[i] === value) {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function appendConnectionHeaderTokens (headersToRemove, connectionHeader) {
|
|
47
|
+
const values = Array.isArray(connectionHeader) ? connectionHeader : [connectionHeader]
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < values.length; i++) {
|
|
50
|
+
const tokens = values[i].split(',')
|
|
51
|
+
for (let j = 0; j < tokens.length; j++) {
|
|
52
|
+
headersToRemove.push(trimOWS(tokens[j]).toLowerCase())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getSameOriginPath (cacheKey, location) {
|
|
58
|
+
if (typeof location !== 'string') {
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let originUrl
|
|
63
|
+
let requestUrl
|
|
64
|
+
let locationUrl
|
|
65
|
+
try {
|
|
66
|
+
originUrl = new URL(cacheKey.origin)
|
|
67
|
+
requestUrl = new URL(cacheKey.path, originUrl)
|
|
68
|
+
locationUrl = new URL(location, requestUrl)
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (locationUrl.origin !== originUrl.origin) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return locationUrl.pathname + locationUrl.search
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function deleteCachedUri (store, cacheKey, path) {
|
|
81
|
+
deleteCachedValue(store, {
|
|
82
|
+
...cacheKey,
|
|
83
|
+
path
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < util.safeHTTPMethods.length; i++) {
|
|
87
|
+
const method = util.safeHTTPMethods[i]
|
|
88
|
+
if (method !== cacheKey.method) {
|
|
89
|
+
deleteCachedValue(store, {
|
|
90
|
+
...cacheKey,
|
|
91
|
+
method,
|
|
92
|
+
path
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function deleteLocationTargets (store, cacheKey, headerValue) {
|
|
99
|
+
if (headerValue === undefined) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const values = Array.isArray(headerValue) ? headerValue : [headerValue]
|
|
104
|
+
for (let i = 0; i < values.length; i++) {
|
|
105
|
+
const path = getSameOriginPath(cacheKey, values[i])
|
|
106
|
+
if (path !== undefined) {
|
|
107
|
+
deleteCachedUri(store, cacheKey, path)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function invalidateUnsafeRequest (store, cacheKey, resHeaders) {
|
|
113
|
+
deleteCachedUri(store, cacheKey, cacheKey.path)
|
|
114
|
+
deleteLocationTargets(store, cacheKey, resHeaders.location)
|
|
115
|
+
deleteLocationTargets(store, cacheKey, resHeaders['content-location'])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
|
|
120
|
+
*
|
|
121
|
+
* @implements {DispatchHandler}
|
|
122
|
+
*/
|
|
123
|
+
class CacheHandler {
|
|
124
|
+
/**
|
|
125
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
|
|
126
|
+
*/
|
|
127
|
+
#cacheKey
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions['type']}
|
|
131
|
+
*/
|
|
132
|
+
#cacheType
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @type {number | undefined}
|
|
136
|
+
*/
|
|
137
|
+
#cacheByDefault
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheStore}
|
|
141
|
+
*/
|
|
142
|
+
#store
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
146
|
+
*/
|
|
147
|
+
#handler
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @type {import('node:stream').Writable | undefined}
|
|
151
|
+
*/
|
|
152
|
+
#writeStream
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
|
|
156
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
157
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
|
|
158
|
+
*/
|
|
159
|
+
constructor ({ store, type, cacheByDefault }, cacheKey, handler) {
|
|
160
|
+
this.#store = store
|
|
161
|
+
this.#cacheType = type
|
|
162
|
+
this.#cacheByDefault = cacheByDefault
|
|
163
|
+
this.#cacheKey = cacheKey
|
|
164
|
+
this.#handler = handler
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onRequestStart (controller, context) {
|
|
168
|
+
this.#writeStream?.destroy()
|
|
169
|
+
this.#writeStream = undefined
|
|
170
|
+
this.#handler.onRequestStart?.(controller, context)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
174
|
+
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
|
|
179
|
+
* @param {number} statusCode
|
|
180
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
181
|
+
* @param {string} statusMessage
|
|
182
|
+
*/
|
|
183
|
+
onResponseStart (
|
|
184
|
+
controller,
|
|
185
|
+
statusCode,
|
|
186
|
+
resHeaders,
|
|
187
|
+
statusMessage
|
|
188
|
+
) {
|
|
189
|
+
const downstreamOnHeaders = () =>
|
|
190
|
+
this.#handler.onResponseStart?.(
|
|
191
|
+
controller,
|
|
192
|
+
statusCode,
|
|
193
|
+
resHeaders,
|
|
194
|
+
statusMessage
|
|
195
|
+
)
|
|
196
|
+
const handler = this
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
!arrayIncludes(util.safeHTTPMethods, this.#cacheKey.method) &&
|
|
200
|
+
statusCode >= 200 &&
|
|
201
|
+
statusCode <= 399
|
|
202
|
+
) {
|
|
203
|
+
// Successful response to an unsafe method, delete it from cache
|
|
204
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
|
|
205
|
+
invalidateUnsafeRequest(this.#store, this.#cacheKey, resHeaders)
|
|
206
|
+
return downstreamOnHeaders()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const cacheControlHeader = resHeaders['cache-control']
|
|
210
|
+
const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
|
|
211
|
+
|
|
212
|
+
if (revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives)) {
|
|
213
|
+
deleteCachedValue(this.#store, this.#cacheKey)
|
|
214
|
+
return downstreamOnHeaders()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const heuristicallyCacheable = resHeaders['last-modified'] && arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode)
|
|
218
|
+
if (
|
|
219
|
+
!cacheControlHeader &&
|
|
220
|
+
!resHeaders['expires'] &&
|
|
221
|
+
!heuristicallyCacheable &&
|
|
222
|
+
!this.#cacheByDefault
|
|
223
|
+
) {
|
|
224
|
+
if (statusCode === 304 && resHeaders.vary && isInvalidOrWildcardVaryHeader(resHeaders.vary)) {
|
|
225
|
+
deleteCachedValue(this.#store, this.#cacheKey)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Don't have anything to tell us this response is cachable and we're not
|
|
229
|
+
// caching by default
|
|
230
|
+
return downstreamOnHeaders()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!canCacheResponse(this.#cacheType, this.#cacheKey.method, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
|
|
234
|
+
if (statusCode === 304 && (cacheControlHeader || revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives))) {
|
|
235
|
+
deleteCachedValue(this.#store, this.#cacheKey)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return downstreamOnHeaders()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const now = Date.now()
|
|
242
|
+
const resAge = Object.hasOwn(resHeaders, 'age') ? getAge(resHeaders.age) : undefined
|
|
243
|
+
if (resAge !== undefined && resAge >= MAX_RESPONSE_AGE) {
|
|
244
|
+
// Response considered stale
|
|
245
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
246
|
+
return downstreamOnHeaders()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const resDate = Object.hasOwn(resHeaders, 'date') ? getDate(resHeaders.date) : undefined
|
|
250
|
+
if (resDate === null) {
|
|
251
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
252
|
+
return downstreamOnHeaders()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const apparentAge = resDate ? Math.max(0, now - resDate.getTime()) : 0
|
|
256
|
+
const currentAge = Math.max(apparentAge, resAge ?? 0)
|
|
257
|
+
|
|
258
|
+
const staleAt =
|
|
259
|
+
determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
|
|
260
|
+
this.#cacheByDefault
|
|
261
|
+
if (staleAt === undefined || currentAge >= staleAt) {
|
|
262
|
+
if (cacheControlHeader || staleAt !== undefined) {
|
|
263
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return downstreamOnHeaders()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const baseTime = now - currentAge
|
|
270
|
+
const absoluteStaleAt = staleAt + baseTime
|
|
271
|
+
if (now >= absoluteStaleAt) {
|
|
272
|
+
// Response is already stale
|
|
273
|
+
deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
|
|
274
|
+
return downstreamOnHeaders()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let varyDirectives
|
|
278
|
+
if (this.#cacheKey.headers && resHeaders.vary) {
|
|
279
|
+
varyDirectives = parseVaryHeader(resHeaders.vary, this.#cacheKey.headers)
|
|
280
|
+
if (!varyDirectives) {
|
|
281
|
+
// Parse error
|
|
282
|
+
return downstreamOnHeaders()
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const cachedAt = baseTime
|
|
287
|
+
const deleteAt = determineDeleteAt(baseTime, now, cacheControlDirectives, absoluteStaleAt)
|
|
288
|
+
const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives)
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
|
|
292
|
+
*/
|
|
293
|
+
const value = {
|
|
294
|
+
statusCode,
|
|
295
|
+
statusMessage,
|
|
296
|
+
headers: strippedHeaders,
|
|
297
|
+
vary: varyDirectives,
|
|
298
|
+
cacheControlDirectives,
|
|
299
|
+
cachedAt,
|
|
300
|
+
staleAt: absoluteStaleAt,
|
|
301
|
+
deleteAt
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Not modified, re-use the cached value
|
|
305
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-304-not-modified
|
|
306
|
+
if (statusCode === 304) {
|
|
307
|
+
const handle304 = (cachedValue) => {
|
|
308
|
+
if (!cachedValue) {
|
|
309
|
+
// Do not create a new cache entry, as a 304 won't have a body - so cannot be cached.
|
|
310
|
+
return downstreamOnHeaders()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Re-use the cached value: statuscode, statusmessage, headers and body
|
|
314
|
+
value.statusCode = cachedValue.statusCode
|
|
315
|
+
value.statusMessage = cachedValue.statusMessage
|
|
316
|
+
value.etag = cachedValue.etag
|
|
317
|
+
value.vary = varyDirectives ?? cachedValue.vary
|
|
318
|
+
value.headers = { ...cachedValue.headers, ...strippedHeaders }
|
|
319
|
+
|
|
320
|
+
downstreamOnHeaders()
|
|
321
|
+
|
|
322
|
+
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
|
|
323
|
+
|
|
324
|
+
if (!this.#writeStream || !cachedValue?.body) {
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (typeof cachedValue.body.values === 'function') {
|
|
329
|
+
const bodyIterator = cachedValue.body.values()
|
|
330
|
+
|
|
331
|
+
const streamCachedBody = () => {
|
|
332
|
+
for (const chunk of bodyIterator) {
|
|
333
|
+
const full = this.#writeStream.write(chunk) === false
|
|
334
|
+
this.#handler.onResponseData?.(controller, chunk)
|
|
335
|
+
// when stream is full stop writing until we get a 'drain' event
|
|
336
|
+
if (full) {
|
|
337
|
+
break
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.#writeStream
|
|
343
|
+
.on('error', function () {
|
|
344
|
+
handler.#writeStream = undefined
|
|
345
|
+
handler.#store.delete(handler.#cacheKey)
|
|
346
|
+
})
|
|
347
|
+
.on('drain', () => {
|
|
348
|
+
streamCachedBody()
|
|
349
|
+
})
|
|
350
|
+
.on('close', function () {
|
|
351
|
+
if (handler.#writeStream === this) {
|
|
352
|
+
handler.#writeStream = undefined
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
streamCachedBody()
|
|
357
|
+
} else if (typeof cachedValue.body.on === 'function') {
|
|
358
|
+
// Readable stream body (e.g. from async/remote cache stores)
|
|
359
|
+
cachedValue.body
|
|
360
|
+
.on('data', (chunk) => {
|
|
361
|
+
this.#writeStream.write(chunk)
|
|
362
|
+
this.#handler.onResponseData?.(controller, chunk)
|
|
363
|
+
})
|
|
364
|
+
.on('end', () => {
|
|
365
|
+
this.#writeStream.end()
|
|
366
|
+
})
|
|
367
|
+
.on('error', () => {
|
|
368
|
+
this.#writeStream = undefined
|
|
369
|
+
this.#store.delete(this.#cacheKey)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
this.#writeStream
|
|
373
|
+
.on('error', function () {
|
|
374
|
+
handler.#writeStream = undefined
|
|
375
|
+
handler.#store.delete(handler.#cacheKey)
|
|
376
|
+
})
|
|
377
|
+
.on('close', function () {
|
|
378
|
+
if (handler.#writeStream === this) {
|
|
379
|
+
handler.#writeStream = undefined
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
|
|
387
|
+
*/
|
|
388
|
+
const result = this.#store.get(this.#cacheKey)
|
|
389
|
+
if (result && typeof result.then === 'function') {
|
|
390
|
+
result.then(handle304)
|
|
391
|
+
} else {
|
|
392
|
+
handle304(result)
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
if (typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) {
|
|
396
|
+
value.etag = resHeaders.etag
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
|
|
400
|
+
|
|
401
|
+
if (!this.#writeStream) {
|
|
402
|
+
return downstreamOnHeaders()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.#writeStream
|
|
406
|
+
.on('drain', () => controller.resume())
|
|
407
|
+
.on('error', function () {
|
|
408
|
+
// TODO (fix): Make error somehow observable?
|
|
409
|
+
handler.#writeStream = undefined
|
|
410
|
+
|
|
411
|
+
// Delete the value in case the cache store is holding onto state from
|
|
412
|
+
// the call to createWriteStream
|
|
413
|
+
handler.#store.delete(handler.#cacheKey)
|
|
414
|
+
})
|
|
415
|
+
.on('close', function () {
|
|
416
|
+
if (handler.#writeStream === this) {
|
|
417
|
+
handler.#writeStream = undefined
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// TODO (fix): Should we resume even if was paused downstream?
|
|
421
|
+
controller.resume()
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
downstreamOnHeaders()
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
onResponseData (controller, chunk) {
|
|
429
|
+
if (this.#writeStream?.write(chunk) === false) {
|
|
430
|
+
controller.pause()
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.#handler.onResponseData?.(controller, chunk)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
onResponseEnd (controller, trailers) {
|
|
437
|
+
this.#writeStream?.end()
|
|
438
|
+
this.#handler.onResponseEnd?.(controller, trailers)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
onResponseError (controller, err) {
|
|
442
|
+
this.#writeStream?.destroy(err)
|
|
443
|
+
this.#writeStream = undefined
|
|
444
|
+
this.#handler.onResponseError?.(controller, err)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheStore} store
|
|
450
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
451
|
+
*/
|
|
452
|
+
function deleteCachedValue (store, cacheKey) {
|
|
453
|
+
try {
|
|
454
|
+
store.delete(cacheKey)?.catch?.(noop)
|
|
455
|
+
} catch {
|
|
456
|
+
// Fail silently
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function deleteCachedValueIfNotModified (statusCode, store, cacheKey) {
|
|
461
|
+
if (statusCode === 304) {
|
|
462
|
+
deleteCachedValue(store, cacheKey)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
468
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
469
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
470
|
+
* @returns {boolean}
|
|
471
|
+
*/
|
|
472
|
+
function revalidationResponseDisallowsCachedReuse (cacheType, resHeaders, cacheControlDirectives) {
|
|
473
|
+
return cacheControlDirectives['no-store'] === true ||
|
|
474
|
+
(cacheType === 'shared' && (
|
|
475
|
+
cacheControlDirectives.private === true ||
|
|
476
|
+
Object.hasOwn(resHeaders, 'set-cookie')
|
|
477
|
+
)) ||
|
|
478
|
+
(resHeaders.vary ? isInvalidOrWildcardVaryHeader(resHeaders.vary) : false)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
|
483
|
+
*
|
|
484
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
485
|
+
* @param {string} method
|
|
486
|
+
* @param {number} statusCode
|
|
487
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
488
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
489
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} [reqHeaders]
|
|
490
|
+
*/
|
|
491
|
+
function canCacheResponse (cacheType, method, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
|
|
492
|
+
if (!arrayIncludes(util.safeHTTPMethods, method)) {
|
|
493
|
+
return false
|
|
494
|
+
}
|
|
495
|
+
// Status code must be final and understood.
|
|
496
|
+
if (statusCode < 200 || arrayIncludes(NOT_UNDERSTOOD_STATUS_CODES, statusCode)) {
|
|
497
|
+
return false
|
|
498
|
+
}
|
|
499
|
+
// Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
|
|
500
|
+
// directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
|
|
501
|
+
if (!arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode) && !resHeaders['expires'] &&
|
|
502
|
+
!cacheControlDirectives.public &&
|
|
503
|
+
cacheControlDirectives['max-age'] === undefined &&
|
|
504
|
+
// RFC 9111: a private response directive, if the cache is not shared
|
|
505
|
+
!(cacheControlDirectives.private && cacheType === 'private') &&
|
|
506
|
+
!(cacheControlDirectives['s-maxage'] !== undefined && cacheType === 'shared')
|
|
507
|
+
) {
|
|
508
|
+
return false
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (cacheControlDirectives['no-store']) {
|
|
512
|
+
return false
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (cacheType === 'shared' && (
|
|
516
|
+
cacheControlDirectives.private === true ||
|
|
517
|
+
Object.hasOwn(resHeaders, 'set-cookie')
|
|
518
|
+
)) {
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
|
|
523
|
+
if (resHeaders.vary && hasVaryStar(resHeaders.vary)) {
|
|
524
|
+
return false
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
|
528
|
+
if (reqHeaders != null && Object.hasOwn(reqHeaders, 'authorization')) {
|
|
529
|
+
if (
|
|
530
|
+
!cacheControlDirectives.public &&
|
|
531
|
+
!cacheControlDirectives['s-maxage'] &&
|
|
532
|
+
!cacheControlDirectives['must-revalidate']
|
|
533
|
+
) {
|
|
534
|
+
return false
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (typeof reqHeaders.authorization !== 'string') {
|
|
538
|
+
return false
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (
|
|
542
|
+
Array.isArray(cacheControlDirectives['no-cache']) &&
|
|
543
|
+
arrayIncludes(cacheControlDirectives['no-cache'], 'authorization')
|
|
544
|
+
) {
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
Array.isArray(cacheControlDirectives['private']) &&
|
|
550
|
+
arrayIncludes(cacheControlDirectives['private'], 'authorization')
|
|
551
|
+
) {
|
|
552
|
+
return false
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return true
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* @param {string | string[]} dateHeader
|
|
561
|
+
* @returns {Date | null | undefined}
|
|
562
|
+
*/
|
|
563
|
+
function getDate (dateHeader) {
|
|
564
|
+
let dateValue = dateHeader
|
|
565
|
+
if (Array.isArray(dateValue)) {
|
|
566
|
+
if (dateValue.length !== 1) {
|
|
567
|
+
return null
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
dateValue = dateValue[0]
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (typeof dateValue !== 'string') {
|
|
574
|
+
return null
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return parseHttpDate(dateValue)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @param {string | string[]} ageHeader
|
|
582
|
+
* @returns {number | undefined}
|
|
583
|
+
*/
|
|
584
|
+
function getAge (ageHeader) {
|
|
585
|
+
let ageValue = ageHeader
|
|
586
|
+
if (Array.isArray(ageValue)) {
|
|
587
|
+
if (ageValue.length !== 1) {
|
|
588
|
+
return MAX_RESPONSE_AGE
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
ageValue = ageValue[0]
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (typeof ageValue !== 'string' || !/^[\t ]*[0-9]+[\t ]*$/.test(ageValue)) {
|
|
595
|
+
return MAX_RESPONSE_AGE
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const age = BigInt(ageValue.replace(/^[\t ]+|[\t ]+$/g, ''))
|
|
599
|
+
if (age >= BigInt(MAX_RESPONSE_AGE / 1000)) {
|
|
600
|
+
return MAX_RESPONSE_AGE
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return Number(age) * 1000
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
608
|
+
* @param {number} now
|
|
609
|
+
* @param {number | undefined} age
|
|
610
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
611
|
+
* @param {Date | undefined} responseDate
|
|
612
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
613
|
+
*
|
|
614
|
+
* @returns {number | undefined} time that the value is stale at in seconds or undefined if it shouldn't be cached
|
|
615
|
+
*/
|
|
616
|
+
function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives) {
|
|
617
|
+
if (cacheType === 'shared') {
|
|
618
|
+
// Prioritize s-maxage since we're a shared cache
|
|
619
|
+
// s-maxage > max-age > Expire
|
|
620
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
|
|
621
|
+
if (hasInvalidCacheControlDirective(cacheControlDirectives, 's-maxage')) {
|
|
622
|
+
return 0
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const sMaxAge = cacheControlDirectives['s-maxage']
|
|
626
|
+
if (sMaxAge !== undefined) {
|
|
627
|
+
return sMaxAge * 1000
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (hasInvalidCacheControlDirective(cacheControlDirectives, 'max-age')) {
|
|
632
|
+
return 0
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const maxAge = cacheControlDirectives['max-age']
|
|
636
|
+
if (maxAge !== undefined) {
|
|
637
|
+
return maxAge * 1000
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (Object.hasOwn(resHeaders, 'expires')) {
|
|
641
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
|
|
642
|
+
if (typeof resHeaders.expires !== 'string') {
|
|
643
|
+
return 0
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const expiresDate = parseHttpDate(resHeaders.expires)
|
|
647
|
+
if (!expiresDate) {
|
|
648
|
+
return 0
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (now >= expiresDate.getTime()) {
|
|
652
|
+
return 0
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (responseDate) {
|
|
656
|
+
if (responseDate >= expiresDate) {
|
|
657
|
+
return 0
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const freshnessLifetime = expiresDate.getTime() - responseDate.getTime()
|
|
661
|
+
if (age !== undefined && age >= freshnessLifetime) {
|
|
662
|
+
return 0
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return freshnessLifetime
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return expiresDate.getTime() - now
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (typeof resHeaders['last-modified'] === 'string') {
|
|
672
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
|
|
673
|
+
const lastModified = parseHttpDate(resHeaders['last-modified'])
|
|
674
|
+
if (lastModified) {
|
|
675
|
+
if (lastModified.getTime() >= now) {
|
|
676
|
+
return undefined
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const responseAge = now - lastModified.getTime()
|
|
680
|
+
|
|
681
|
+
return responseAge * 0.1
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (cacheControlDirectives.immutable) {
|
|
686
|
+
// https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
|
|
687
|
+
return 31536000000
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return undefined
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* @param {number} baseTime
|
|
695
|
+
* @param {number} cachedAt
|
|
696
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
697
|
+
* @param {number} staleAt
|
|
698
|
+
*/
|
|
699
|
+
function determineDeleteAt (baseTime, cachedAt, cacheControlDirectives, staleAt) {
|
|
700
|
+
let staleWhileRevalidate = -Infinity
|
|
701
|
+
let staleIfError = -Infinity
|
|
702
|
+
let immutable = -Infinity
|
|
703
|
+
|
|
704
|
+
if (cacheControlDirectives['stale-while-revalidate']) {
|
|
705
|
+
staleWhileRevalidate = staleAt + (cacheControlDirectives['stale-while-revalidate'] * 1000)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (cacheControlDirectives['stale-if-error']) {
|
|
709
|
+
staleIfError = staleAt + (cacheControlDirectives['stale-if-error'] * 1000)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (cacheControlDirectives.immutable && staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
|
|
713
|
+
immutable = cachedAt + 31536000000
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// When no stale directives or immutable flag, add a revalidation buffer
|
|
717
|
+
// equal to the freshness lifetime so the entry survives past staleAt long
|
|
718
|
+
// enough to be revalidated instead of silently disappearing.
|
|
719
|
+
//
|
|
720
|
+
// Response Date headers only have second precision, so baseTime can trail the
|
|
721
|
+
// actual cache insertion time by up to ~1s. Pad the buffer by that bounded
|
|
722
|
+
// skew so short-lived entries do not disappear exactly when they should be
|
|
723
|
+
// revalidated.
|
|
724
|
+
if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity && immutable === -Infinity) {
|
|
725
|
+
const freshnessLifetime = staleAt - baseTime
|
|
726
|
+
const datePrecisionPadding = Math.min(Math.max(cachedAt - baseTime, 0), 1000)
|
|
727
|
+
return staleAt + freshnessLifetime + datePrecisionPadding
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Strips headers required to be removed in cached responses
|
|
735
|
+
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
|
736
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
737
|
+
* @returns {Record<string, string | string []>}
|
|
738
|
+
*/
|
|
739
|
+
function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
|
740
|
+
const headersToRemove = [
|
|
741
|
+
'connection',
|
|
742
|
+
'proxy-authenticate',
|
|
743
|
+
'proxy-authentication-info',
|
|
744
|
+
'proxy-authorization',
|
|
745
|
+
'proxy-connection',
|
|
746
|
+
'te',
|
|
747
|
+
'transfer-encoding',
|
|
748
|
+
'upgrade',
|
|
749
|
+
// We'll add age back when serving it
|
|
750
|
+
'age'
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
if (resHeaders['connection']) {
|
|
754
|
+
appendConnectionHeaderTokens(headersToRemove, resHeaders['connection'])
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (Array.isArray(cacheControlDirectives['no-cache'])) {
|
|
758
|
+
headersToRemove.push(...cacheControlDirectives['no-cache'])
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (Array.isArray(cacheControlDirectives['private'])) {
|
|
762
|
+
headersToRemove.push(...cacheControlDirectives['private'])
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
let strippedHeaders
|
|
766
|
+
for (const headerName of headersToRemove) {
|
|
767
|
+
if (Object.hasOwn(resHeaders, headerName)) {
|
|
768
|
+
strippedHeaders ??= { ...resHeaders }
|
|
769
|
+
delete strippedHeaders[headerName]
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return strippedHeaders ?? resHeaders
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
module.exports = CacheHandler
|