acrylblends 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -1
- package/dist/index.js +28 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +16 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +7 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
- package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
- package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
- package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
- package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
- package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
- package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
- package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
- package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
- package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
- package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
- package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
- package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
- package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
- package/plugins/acryl-node-server/src/index.ts +19 -6
- package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
- package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
- package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- package/plugins/acryl-router/src/client.ts +2 -0
- package/plugins/acryl-router/src/connection.ts +42 -0
- package/plugins/acryl-router/src/core/app-base.ts +544 -0
- package/plugins/acryl-router/src/core/app.test.ts +3833 -0
- package/plugins/acryl-router/src/core/app.ts +34 -0
- package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
- package/plugins/acryl-router/src/core/client/client.ts +249 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
- package/plugins/acryl-router/src/core/client/index.ts +17 -0
- package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
- package/plugins/acryl-router/src/core/client/types.ts +393 -0
- package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
- package/plugins/acryl-router/src/core/client/utils.ts +117 -0
- package/plugins/acryl-router/src/core/compose.test.ts +751 -0
- package/plugins/acryl-router/src/core/compose.ts +73 -0
- package/plugins/acryl-router/src/core/context.test.ts +629 -0
- package/plugins/acryl-router/src/core/context.ts +781 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
- package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
- package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
- package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
- package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
- package/plugins/acryl-router/src/core/http-exception.ts +77 -0
- package/plugins/acryl-router/src/core/index.ts +53 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
- package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
- package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
- package/plugins/acryl-router/src/core/request/constants.ts +1 -0
- package/plugins/acryl-router/src/core/request.test.ts +751 -0
- package/plugins/acryl-router/src/core/request.ts +502 -0
- package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
- package/plugins/acryl-router/src/core/router/utils.ts +1 -0
- package/plugins/acryl-router/src/core/router.ts +103 -0
- package/plugins/acryl-router/src/core/types.test.ts +4134 -0
- package/plugins/acryl-router/src/core/types.ts +2782 -0
- package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
- package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
- package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
- package/plugins/acryl-router/src/core/utils/body.ts +263 -0
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
- package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
- package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
- package/plugins/acryl-router/src/core/utils/color.ts +56 -0
- package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
- package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
- package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
- package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
- package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
- package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
- package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
- package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
- package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
- package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
- package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
- package/plugins/acryl-router/src/core/utils/html.ts +182 -0
- package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
- package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
- package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
- package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
- package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
- package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
- package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
- package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
- package/plugins/acryl-router/src/core/utils/types.ts +123 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
- package/plugins/acryl-router/src/core/utils/url.ts +337 -0
- package/plugins/acryl-router/src/core/validator/index.ts +8 -0
- package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
- package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
- package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
- package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
- package/plugins/acryl-router/src/helpers/index.ts +13 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +19 -13
- package/plugins/acryl-router/src/service.ts +256 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/service.spec.ts +264 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +2 -2
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +56 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* JSON Web Token (JWT)
|
|
4
|
+
* https://datatracker.ietf.org/doc/html/rfc7519
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { decodeBase64Url, encodeBase64Url } from '../encode.ts'
|
|
8
|
+
import { AlgorithmTypes } from './jwa.ts'
|
|
9
|
+
import type { AsymmetricAlgorithm, SignatureAlgorithm, SymmetricAlgorithm } from './jwa.ts'
|
|
10
|
+
import { signing, verifying } from './jws.ts'
|
|
11
|
+
import type { AppJsonWebKey, SignatureKey } from './jws.ts'
|
|
12
|
+
import {
|
|
13
|
+
JwtAlgorithmMismatch,
|
|
14
|
+
JwtAlgorithmNotAllowed,
|
|
15
|
+
JwtAlgorithmRequired,
|
|
16
|
+
JwtHeaderInvalid,
|
|
17
|
+
JwtHeaderRequiresKid,
|
|
18
|
+
JwtPayloadRequiresAud,
|
|
19
|
+
JwtSymmetricAlgorithmNotAllowed,
|
|
20
|
+
JwtTokenAudience,
|
|
21
|
+
JwtTokenExpired,
|
|
22
|
+
JwtTokenInvalid,
|
|
23
|
+
JwtTokenIssuedAt,
|
|
24
|
+
JwtTokenIssuer,
|
|
25
|
+
JwtTokenNotBefore,
|
|
26
|
+
JwtTokenSignatureMismatched,
|
|
27
|
+
} from './types.ts'
|
|
28
|
+
import type { JWTPayload } from './types.ts'
|
|
29
|
+
import { utf8Decoder, utf8Encoder } from './utf8.ts'
|
|
30
|
+
|
|
31
|
+
const encodeJwtPart = (part: unknown): string =>
|
|
32
|
+
encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')
|
|
33
|
+
const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '')
|
|
34
|
+
|
|
35
|
+
const decodeJwtPart = (part: string): TokenHeader | JWTPayload | undefined =>
|
|
36
|
+
JSON.parse(utf8Decoder.decode(decodeBase64Url(part)))
|
|
37
|
+
|
|
38
|
+
export interface TokenHeader {
|
|
39
|
+
alg: SignatureAlgorithm
|
|
40
|
+
typ?: 'JWT'
|
|
41
|
+
kid?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isTokenHeader(obj: unknown): obj is TokenHeader {
|
|
45
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
46
|
+
const objWithAlg = obj as { [key: string]: unknown }
|
|
47
|
+
return (
|
|
48
|
+
'alg' in objWithAlg &&
|
|
49
|
+
Object.values(AlgorithmTypes).includes(objWithAlg.alg as AlgorithmTypes) &&
|
|
50
|
+
(!('typ' in objWithAlg) || objWithAlg.typ === 'JWT')
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const sign = async (
|
|
57
|
+
payload: JWTPayload,
|
|
58
|
+
privateKey: SignatureKey,
|
|
59
|
+
alg: SignatureAlgorithm = 'HS256'
|
|
60
|
+
): Promise<string> => {
|
|
61
|
+
const encodedPayload = encodeJwtPart(payload)
|
|
62
|
+
let encodedHeader
|
|
63
|
+
if (typeof privateKey === 'object' && 'alg' in privateKey) {
|
|
64
|
+
alg = privateKey.alg as SignatureAlgorithm
|
|
65
|
+
encodedHeader = encodeJwtPart({ alg, typ: 'JWT', kid: privateKey.kid })
|
|
66
|
+
} else {
|
|
67
|
+
encodedHeader = encodeJwtPart({ alg, typ: 'JWT' })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const partialToken = `${encodedHeader}.${encodedPayload}`
|
|
71
|
+
|
|
72
|
+
const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken))
|
|
73
|
+
const signature = encodeSignaturePart(signaturePart)
|
|
74
|
+
|
|
75
|
+
return `${partialToken}.${signature}`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type VerifyOptions = {
|
|
79
|
+
/** The expected issuer used for verifying the token */
|
|
80
|
+
iss?: string | RegExp
|
|
81
|
+
/** Verify the `nbf` claim (default: `true`) */
|
|
82
|
+
nbf?: boolean
|
|
83
|
+
/** Verify the `exp` claim (default: `true`) */
|
|
84
|
+
exp?: boolean
|
|
85
|
+
/** Verify the `iat` claim (default: `true`) */
|
|
86
|
+
iat?: boolean
|
|
87
|
+
/** Acceptable audience(s) for the token */
|
|
88
|
+
aud?: string | string[] | RegExp
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type VerifyOptionsWithAlg = {
|
|
92
|
+
/** The algorithm used for decoding the token */
|
|
93
|
+
alg: SignatureAlgorithm
|
|
94
|
+
} & VerifyOptions
|
|
95
|
+
|
|
96
|
+
export const verify = async (
|
|
97
|
+
token: string,
|
|
98
|
+
publicKey: SignatureKey,
|
|
99
|
+
algOrOptions: SignatureAlgorithm | VerifyOptionsWithAlg
|
|
100
|
+
): Promise<JWTPayload> => {
|
|
101
|
+
if (!algOrOptions) {
|
|
102
|
+
throw new JwtAlgorithmRequired()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const {
|
|
106
|
+
alg,
|
|
107
|
+
iss,
|
|
108
|
+
nbf = true,
|
|
109
|
+
exp = true,
|
|
110
|
+
iat = true,
|
|
111
|
+
aud,
|
|
112
|
+
} = typeof algOrOptions === 'string' ? { alg: algOrOptions } : algOrOptions
|
|
113
|
+
|
|
114
|
+
if (!alg) {
|
|
115
|
+
throw new JwtAlgorithmRequired()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const tokenParts = token.split('.')
|
|
119
|
+
if (tokenParts.length !== 3) {
|
|
120
|
+
throw new JwtTokenInvalid(token)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { header, payload } = decode(token)
|
|
124
|
+
if (!isTokenHeader(header)) {
|
|
125
|
+
throw new JwtHeaderInvalid(header)
|
|
126
|
+
}
|
|
127
|
+
if (header.alg !== alg) {
|
|
128
|
+
throw new JwtAlgorithmMismatch(alg, header.alg)
|
|
129
|
+
}
|
|
130
|
+
const now = Math.floor(Date.now() / 1000)
|
|
131
|
+
if (nbf && payload.nbf !== undefined) {
|
|
132
|
+
if (typeof payload.nbf !== 'number' || !Number.isFinite(payload.nbf) || payload.nbf > now) {
|
|
133
|
+
throw new JwtTokenNotBefore(token)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (exp && payload.exp !== undefined) {
|
|
137
|
+
if (typeof payload.exp !== 'number' || !Number.isFinite(payload.exp) || payload.exp <= now) {
|
|
138
|
+
throw new JwtTokenExpired(token)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (iat && payload.iat !== undefined) {
|
|
142
|
+
if (typeof payload.iat !== 'number' || !Number.isFinite(payload.iat) || now < payload.iat) {
|
|
143
|
+
throw new JwtTokenIssuedAt(now, payload.iat)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (iss) {
|
|
147
|
+
if (!payload.iss) {
|
|
148
|
+
throw new JwtTokenIssuer(iss, null)
|
|
149
|
+
}
|
|
150
|
+
if (typeof iss === 'string' && payload.iss !== iss) {
|
|
151
|
+
throw new JwtTokenIssuer(iss, payload.iss)
|
|
152
|
+
}
|
|
153
|
+
if (iss instanceof RegExp && !iss.test(payload.iss)) {
|
|
154
|
+
throw new JwtTokenIssuer(iss, payload.iss)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (aud) {
|
|
159
|
+
if (!payload.aud) {
|
|
160
|
+
throw new JwtPayloadRequiresAud(payload)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]
|
|
164
|
+
const matched = audiences.some((payloadAud): boolean =>
|
|
165
|
+
aud instanceof RegExp
|
|
166
|
+
? aud.test(payloadAud)
|
|
167
|
+
: typeof aud === 'string'
|
|
168
|
+
? payloadAud === aud
|
|
169
|
+
: Array.isArray(aud) && aud.includes(payloadAud)
|
|
170
|
+
)
|
|
171
|
+
if (!matched) {
|
|
172
|
+
throw new JwtTokenAudience(aud, payload.aud)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const headerPayload = token.substring(0, token.lastIndexOf('.'))
|
|
177
|
+
let signature: Uint8Array<ArrayBuffer>
|
|
178
|
+
try {
|
|
179
|
+
signature = decodeBase64Url(tokenParts[2])
|
|
180
|
+
} catch {
|
|
181
|
+
throw new JwtTokenInvalid(token)
|
|
182
|
+
}
|
|
183
|
+
const verified = await verifying(publicKey, alg, signature, utf8Encoder.encode(headerPayload))
|
|
184
|
+
if (!verified) {
|
|
185
|
+
throw new JwtTokenSignatureMismatched(token)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return payload
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Symmetric algorithms that are not allowed for JWK verification
|
|
192
|
+
const symmetricAlgorithms: SymmetricAlgorithm[] = [
|
|
193
|
+
AlgorithmTypes.HS256,
|
|
194
|
+
AlgorithmTypes.HS384,
|
|
195
|
+
AlgorithmTypes.HS512,
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
export const verifyWithJwks = async (
|
|
199
|
+
token: string,
|
|
200
|
+
options: {
|
|
201
|
+
keys?: AppJsonWebKey[]
|
|
202
|
+
jwks_uri?: string
|
|
203
|
+
verification?: VerifyOptions
|
|
204
|
+
allowedAlgorithms: readonly AsymmetricAlgorithm[]
|
|
205
|
+
},
|
|
206
|
+
init?: RequestInit
|
|
207
|
+
): Promise<JWTPayload> => {
|
|
208
|
+
const verifyOpts = options.verification || {}
|
|
209
|
+
|
|
210
|
+
const header = decodeHeader(token)
|
|
211
|
+
|
|
212
|
+
if (!isTokenHeader(header)) {
|
|
213
|
+
throw new JwtHeaderInvalid(header)
|
|
214
|
+
}
|
|
215
|
+
if (!header.kid) {
|
|
216
|
+
throw new JwtHeaderRequiresKid(header)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Reject symmetric algorithms (HS256, HS384, HS512) to prevent algorithm confusion attacks
|
|
220
|
+
if (symmetricAlgorithms.includes(header.alg as SymmetricAlgorithm)) {
|
|
221
|
+
throw new JwtSymmetricAlgorithmNotAllowed(header.alg)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Validate against allowed algorithms
|
|
225
|
+
if (!options.allowedAlgorithms.includes(header.alg as AsymmetricAlgorithm)) {
|
|
226
|
+
throw new JwtAlgorithmNotAllowed(header.alg, options.allowedAlgorithms)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let verifyKeys = options.keys ? [...options.keys] : undefined
|
|
230
|
+
|
|
231
|
+
if (options.jwks_uri) {
|
|
232
|
+
const response = await fetch(options.jwks_uri, init)
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
throw new Error(`failed to fetch JWKS from ${options.jwks_uri}`)
|
|
235
|
+
}
|
|
236
|
+
const data = (await response.json()) as { keys?: JsonWebKey[] }
|
|
237
|
+
if (!data.keys) {
|
|
238
|
+
throw new Error('invalid JWKS response. "keys" field is missing')
|
|
239
|
+
}
|
|
240
|
+
if (!Array.isArray(data.keys)) {
|
|
241
|
+
throw new Error('invalid JWKS response. "keys" field is not an array')
|
|
242
|
+
}
|
|
243
|
+
verifyKeys ??= []
|
|
244
|
+
verifyKeys.push(...(data.keys as AppJsonWebKey[]))
|
|
245
|
+
} else if (!verifyKeys) {
|
|
246
|
+
throw new Error('verifyWithJwks requires options for either "keys" or "jwks_uri" or both')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const matchingKey = verifyKeys.find((key) => key.kid === header.kid)
|
|
250
|
+
if (!matchingKey) {
|
|
251
|
+
throw new JwtTokenInvalid(token)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Verify that JWK's alg matches JWT header's alg when JWK has alg field
|
|
255
|
+
if (matchingKey.alg && matchingKey.alg !== header.alg) {
|
|
256
|
+
throw new JwtAlgorithmMismatch(matchingKey.alg, header.alg)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return await verify(token, matchingKey, {
|
|
260
|
+
alg: header.alg,
|
|
261
|
+
...verifyOpts,
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const decode = (token: string): { header: TokenHeader; payload: JWTPayload } => {
|
|
266
|
+
const parts = token.split('.')
|
|
267
|
+
if (parts.length !== 3) {
|
|
268
|
+
throw new JwtTokenInvalid(token)
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const header = decodeJwtPart(parts[0]) as TokenHeader
|
|
272
|
+
const payload = decodeJwtPart(parts[1]) as JWTPayload
|
|
273
|
+
return {
|
|
274
|
+
header,
|
|
275
|
+
payload,
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
throw new JwtTokenInvalid(token)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const decodeHeader = (token: string): TokenHeader => {
|
|
283
|
+
const parts = token.split('.')
|
|
284
|
+
if (parts.length !== 3) {
|
|
285
|
+
throw new JwtTokenInvalid(token)
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
return decodeJwtPart(parts[0]) as TokenHeader
|
|
289
|
+
} catch {
|
|
290
|
+
throw new JwtTokenInvalid(token)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Type definitions for JWT utilities.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class JwtAlgorithmNotImplemented extends Error {
|
|
7
|
+
constructor(alg: string) {
|
|
8
|
+
super(`${alg} is not an implemented algorithm`)
|
|
9
|
+
this.name = 'JwtAlgorithmNotImplemented'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class JwtAlgorithmRequired extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('JWT verification requires "alg" option to be specified')
|
|
16
|
+
this.name = 'JwtAlgorithmRequired'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class JwtAlgorithmMismatch extends Error {
|
|
21
|
+
constructor(expected: string, actual: string) {
|
|
22
|
+
super(`JWT algorithm mismatch: expected "${expected}", got "${actual}"`)
|
|
23
|
+
this.name = 'JwtAlgorithmMismatch'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class JwtTokenInvalid extends Error {
|
|
28
|
+
constructor(token: string) {
|
|
29
|
+
super(`invalid JWT token: ${token}`)
|
|
30
|
+
this.name = 'JwtTokenInvalid'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class JwtTokenNotBefore extends Error {
|
|
35
|
+
constructor(token: string) {
|
|
36
|
+
super(`token (${token}) is being used before it's valid`)
|
|
37
|
+
this.name = 'JwtTokenNotBefore'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class JwtTokenExpired extends Error {
|
|
42
|
+
constructor(token: string) {
|
|
43
|
+
super(`token (${token}) expired`)
|
|
44
|
+
this.name = 'JwtTokenExpired'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class JwtTokenIssuedAt extends Error {
|
|
49
|
+
constructor(currentTimestamp: number, iat: number) {
|
|
50
|
+
super(
|
|
51
|
+
`Invalid "iat" claim, must be a valid number lower than "${currentTimestamp}" (iat: "${iat}")`
|
|
52
|
+
)
|
|
53
|
+
this.name = 'JwtTokenIssuedAt'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class JwtTokenIssuer extends Error {
|
|
58
|
+
constructor(expected: string | RegExp, iss: string | null) {
|
|
59
|
+
super(`expected issuer "${expected}", got ${iss ? `"${iss}"` : 'none'} `)
|
|
60
|
+
this.name = 'JwtTokenIssuer'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class JwtHeaderInvalid extends Error {
|
|
65
|
+
constructor(header: object) {
|
|
66
|
+
super(`jwt header is invalid: ${JSON.stringify(header)}`)
|
|
67
|
+
this.name = 'JwtHeaderInvalid'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class JwtHeaderRequiresKid extends Error {
|
|
72
|
+
constructor(header: object) {
|
|
73
|
+
super(`required "kid" in jwt header: ${JSON.stringify(header)}`)
|
|
74
|
+
this.name = 'JwtHeaderRequiresKid'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class JwtSymmetricAlgorithmNotAllowed extends Error {
|
|
79
|
+
constructor(alg: string) {
|
|
80
|
+
super(`symmetric algorithm "${alg}" is not allowed for JWK verification`)
|
|
81
|
+
this.name = 'JwtSymmetricAlgorithmNotAllowed'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class JwtAlgorithmNotAllowed extends Error {
|
|
86
|
+
constructor(alg: string, allowedAlgorithms: readonly string[]) {
|
|
87
|
+
super(`algorithm "${alg}" is not in the allowed list: [${allowedAlgorithms.join(', ')}]`)
|
|
88
|
+
this.name = 'JwtAlgorithmNotAllowed'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class JwtTokenSignatureMismatched extends Error {
|
|
93
|
+
constructor(token: string) {
|
|
94
|
+
super(`token(${token}) signature mismatched`)
|
|
95
|
+
this.name = 'JwtTokenSignatureMismatched'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class JwtPayloadRequiresAud extends Error {
|
|
100
|
+
constructor(payload: object) {
|
|
101
|
+
super(`required "aud" in jwt payload: ${JSON.stringify(payload)}`)
|
|
102
|
+
this.name = 'JwtPayloadRequiresAud'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class JwtTokenAudience extends Error {
|
|
107
|
+
constructor(expected: string | string[] | RegExp, aud: string | string[]) {
|
|
108
|
+
super(
|
|
109
|
+
`expected audience "${
|
|
110
|
+
Array.isArray(expected) ? expected.join(', ') : expected
|
|
111
|
+
}", got "${aud}"`
|
|
112
|
+
)
|
|
113
|
+
this.name = 'JwtTokenAudience'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const CryptoKeyUsage = {
|
|
118
|
+
Encrypt: 'encrypt',
|
|
119
|
+
Decrypt: 'decrypt',
|
|
120
|
+
Sign: 'sign',
|
|
121
|
+
Verify: 'verify',
|
|
122
|
+
DeriveKey: 'deriveKey',
|
|
123
|
+
DeriveBits: 'deriveBits',
|
|
124
|
+
WrapKey: 'wrapKey',
|
|
125
|
+
UnwrapKey: 'unwrapKey',
|
|
126
|
+
} as const
|
|
127
|
+
export type CryptoKeyUsage = (typeof CryptoKeyUsage)[keyof typeof CryptoKeyUsage]
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* JWT Payload
|
|
131
|
+
*/
|
|
132
|
+
export type JWTPayload = {
|
|
133
|
+
[key: string]: unknown
|
|
134
|
+
/**
|
|
135
|
+
* The token is checked to ensure it has not expired.
|
|
136
|
+
*/
|
|
137
|
+
exp?: number
|
|
138
|
+
/**
|
|
139
|
+
* The token is checked to ensure it is not being used before a specified time.
|
|
140
|
+
*/
|
|
141
|
+
nbf?: number
|
|
142
|
+
/**
|
|
143
|
+
* The token is checked to ensure it is not issued in the future.
|
|
144
|
+
*/
|
|
145
|
+
iat?: number
|
|
146
|
+
/**
|
|
147
|
+
* The token is checked to ensure it has been issued by a trusted issuer.
|
|
148
|
+
*/
|
|
149
|
+
iss?: string
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The token is checked to ensure it is intended for a specific audience.
|
|
153
|
+
*/
|
|
154
|
+
aud?: string | string[]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type { AppJsonWebKey } from './jws.ts'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getExtension, getMimeType } from './mime.ts'
|
|
2
|
+
|
|
3
|
+
const mime = {
|
|
4
|
+
m3u8: 'application/vnd.apple.mpegurl',
|
|
5
|
+
ts: 'video/mp2t',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('mime', () => {
|
|
9
|
+
it('getMimeType', () => {
|
|
10
|
+
expect(getMimeType('hello.txt')).toBe('text/plain; charset=utf-8')
|
|
11
|
+
expect(getMimeType('hello.html')).toBe('text/html; charset=utf-8')
|
|
12
|
+
expect(getMimeType('hello.json')).toBe('application/json')
|
|
13
|
+
expect(getMimeType('favicon.ico')).toBe('image/x-icon')
|
|
14
|
+
expect(getMimeType('good.morning.hello.gif')).toBe('image/gif')
|
|
15
|
+
expect(getMimeType('site.webmanifest')).toBe('application/manifest+json')
|
|
16
|
+
expect(getMimeType('goodmorninghellogif')).toBeUndefined()
|
|
17
|
+
expect(getMimeType('indexjs.abcd')).toBeUndefined()
|
|
18
|
+
expect(getMimeType('IMAGE.JPG')).toBe('image/jpeg')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('getMimeType returns charset parameter for text and XML-based formats', () => {
|
|
22
|
+
expect(getMimeType('icon.svg')).toBe('image/svg+xml; charset=utf-8')
|
|
23
|
+
expect(getMimeType('page.xhtml')).toBe('application/xhtml+xml; charset=utf-8')
|
|
24
|
+
expect(getMimeType('feed.xml')).toBe('application/xml; charset=utf-8')
|
|
25
|
+
expect(getMimeType('events.ics')).toBe('text/calendar; charset=utf-8')
|
|
26
|
+
expect(getMimeType('script.mjs')).toBe('text/javascript; charset=utf-8')
|
|
27
|
+
expect(getMimeType('style.css')).toBe('text/css; charset=utf-8')
|
|
28
|
+
expect(getMimeType('data.csv')).toBe('text/csv; charset=utf-8')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('getMimeType with custom mime', () => {
|
|
32
|
+
expect(getMimeType('morning-routine.m3u8', mime)).toBe('application/vnd.apple.mpegurl')
|
|
33
|
+
expect(getMimeType('morning-routine1.ts', mime)).toBe('video/mp2t')
|
|
34
|
+
expect(getMimeType('readme.txt', mime)).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('getExtension', () => {
|
|
38
|
+
expect(getExtension('audio/aac')).toBe('aac')
|
|
39
|
+
expect(getExtension('video/x-msvideo')).toBe('avi')
|
|
40
|
+
expect(getExtension('image/avif')).toBe('avif')
|
|
41
|
+
expect(getExtension('text/css')).toBe('css')
|
|
42
|
+
expect(getExtension('text/html')).toBe('htm')
|
|
43
|
+
expect(getExtension('image/jpeg')).toBe('jpeg')
|
|
44
|
+
expect(getExtension('text/javascript')).toBe('js')
|
|
45
|
+
expect(getExtension('application/json')).toBe('json')
|
|
46
|
+
expect(getExtension('audio/mpeg')).toBe('mp3')
|
|
47
|
+
expect(getExtension('video/mp4')).toBe('mp4')
|
|
48
|
+
expect(getExtension('application/pdf')).toBe('pdf')
|
|
49
|
+
expect(getExtension('image/png')).toBe('png')
|
|
50
|
+
expect(getExtension('application/zip')).toBe('zip')
|
|
51
|
+
expect(getExtension('non/existent')).toBeUndefined()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('getExtension matches MIME types regardless of charset/parameters', () => {
|
|
55
|
+
expect(getExtension('text/html; charset=utf-8')).toBe('htm')
|
|
56
|
+
expect(getExtension('text/plain; charset=utf-8')).toBe('txt')
|
|
57
|
+
expect(getExtension('image/svg+xml; charset=utf-8')).toBe('svg')
|
|
58
|
+
expect(getExtension('application/xml; charset=utf-8')).toBe('xml')
|
|
59
|
+
expect(getExtension('application/xhtml+xml; charset=utf-8')).toBe('xhtml')
|
|
60
|
+
expect(getExtension('text/css; charset=utf-16')).toBe('css')
|
|
61
|
+
expect(getExtension('image/svg+xml')).toBe('svg')
|
|
62
|
+
expect(getExtension('application/xml')).toBe('xml')
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* MIME utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const getMimeType = (
|
|
7
|
+
filename: string,
|
|
8
|
+
mimes: Record<string, string> = baseMimes
|
|
9
|
+
): string | undefined => {
|
|
10
|
+
const regexp = /\.([a-zA-Z0-9]+?)$/
|
|
11
|
+
const match = filename.match(regexp)
|
|
12
|
+
if (!match) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
return mimes[match[1].toLowerCase()]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getExtension = (mimeType: string): string | undefined => {
|
|
19
|
+
const baseType = mimeType.split(';', 1)[0].trim()
|
|
20
|
+
for (const ext in baseMimes) {
|
|
21
|
+
const stored = baseMimes[ext]
|
|
22
|
+
if (stored === mimeType || stored.split(';', 1)[0].trim() === baseType) {
|
|
23
|
+
return ext
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { baseMimes as mimes }
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Union types for BaseMime
|
|
32
|
+
*/
|
|
33
|
+
export type BaseMime = (typeof _baseMimes)[keyof typeof _baseMimes]
|
|
34
|
+
|
|
35
|
+
const _baseMimes = {
|
|
36
|
+
aac: 'audio/aac',
|
|
37
|
+
avi: 'video/x-msvideo',
|
|
38
|
+
avif: 'image/avif',
|
|
39
|
+
av1: 'video/av1',
|
|
40
|
+
bin: 'application/octet-stream',
|
|
41
|
+
bmp: 'image/bmp',
|
|
42
|
+
css: 'text/css; charset=utf-8',
|
|
43
|
+
csv: 'text/csv; charset=utf-8',
|
|
44
|
+
eot: 'application/vnd.ms-fontobject',
|
|
45
|
+
epub: 'application/epub+zip',
|
|
46
|
+
gif: 'image/gif',
|
|
47
|
+
gz: 'application/gzip',
|
|
48
|
+
htm: 'text/html; charset=utf-8',
|
|
49
|
+
html: 'text/html; charset=utf-8',
|
|
50
|
+
ico: 'image/x-icon',
|
|
51
|
+
ics: 'text/calendar; charset=utf-8',
|
|
52
|
+
jpeg: 'image/jpeg',
|
|
53
|
+
jpg: 'image/jpeg',
|
|
54
|
+
js: 'text/javascript; charset=utf-8',
|
|
55
|
+
json: 'application/json',
|
|
56
|
+
jsonld: 'application/ld+json',
|
|
57
|
+
map: 'application/json',
|
|
58
|
+
mid: 'audio/x-midi',
|
|
59
|
+
midi: 'audio/x-midi',
|
|
60
|
+
mjs: 'text/javascript; charset=utf-8',
|
|
61
|
+
mp3: 'audio/mpeg',
|
|
62
|
+
mp4: 'video/mp4',
|
|
63
|
+
mpeg: 'video/mpeg',
|
|
64
|
+
oga: 'audio/ogg',
|
|
65
|
+
ogv: 'video/ogg',
|
|
66
|
+
ogx: 'application/ogg',
|
|
67
|
+
opus: 'audio/opus',
|
|
68
|
+
otf: 'font/otf',
|
|
69
|
+
pdf: 'application/pdf',
|
|
70
|
+
png: 'image/png',
|
|
71
|
+
rtf: 'application/rtf',
|
|
72
|
+
svg: 'image/svg+xml; charset=utf-8',
|
|
73
|
+
tif: 'image/tiff',
|
|
74
|
+
tiff: 'image/tiff',
|
|
75
|
+
ts: 'video/mp2t',
|
|
76
|
+
ttf: 'font/ttf',
|
|
77
|
+
txt: 'text/plain; charset=utf-8',
|
|
78
|
+
wasm: 'application/wasm',
|
|
79
|
+
webm: 'video/webm',
|
|
80
|
+
weba: 'audio/webm',
|
|
81
|
+
webmanifest: 'application/manifest+json',
|
|
82
|
+
webp: 'image/webp',
|
|
83
|
+
woff: 'font/woff',
|
|
84
|
+
woff2: 'font/woff2',
|
|
85
|
+
xhtml: 'application/xhtml+xml; charset=utf-8',
|
|
86
|
+
xml: 'application/xml; charset=utf-8',
|
|
87
|
+
zip: 'application/zip',
|
|
88
|
+
'3gp': 'video/3gpp',
|
|
89
|
+
'3g2': 'video/3gpp2',
|
|
90
|
+
gltf: 'model/gltf+json',
|
|
91
|
+
glb: 'model/gltf-binary',
|
|
92
|
+
} as const
|
|
93
|
+
|
|
94
|
+
const baseMimes: Record<string, BaseMime> = _baseMimes
|