acrylblends 0.2.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 +22 -16
- package/dist/index.js +65 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/acryl-inspect/LICENSE +21 -0
- package/plugins/acryl-inspect/plugin.json +12 -0
- package/plugins/acryl-inspect/src/index.ts +3 -0
- package/plugins/acryl-inspect/src/report.ts +126 -0
- package/plugins/acryl-inspect/src/service.ts +68 -0
- package/plugins/acryl-inspect/tests/report.spec.ts +63 -0
- package/plugins/acryl-inspect/tests/service.spec.ts +121 -0
- package/plugins/acryl-inspect/upstream.json +18 -0
- package/plugins/acryl-node-server/LICENSE +47 -0
- package/plugins/acryl-node-server/plugin.json +13 -0
- 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 +107 -0
- 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 +141 -0
- package/plugins/acryl-node-server/upstream.json +18 -0
- package/plugins/acryl-router/LICENSE +47 -0
- package/plugins/acryl-router/plugin.json +13 -0
- 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 -0
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -0
- package/plugins/acryl-router/src/middleware/index.ts +25 -0
- package/plugins/acryl-router/src/middleware/loopback-only.ts +88 -0
- package/plugins/acryl-router/src/service.ts +356 -0
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +64 -0
- package/plugins/acryl-router/tests/loader.spec.ts +72 -0
- package/plugins/acryl-router/tests/service.spec.ts +409 -0
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +20 -0
- package/plugins/acryl-router-http/LICENSE +21 -0
- package/plugins/acryl-router-http/plugin.json +12 -0
- package/plugins/acryl-router-http/src/bridge.ts +54 -0
- package/plugins/acryl-router-http/src/index.ts +63 -0
- package/plugins/acryl-router-http/src/serve.ts +24 -0
- package/plugins/acryl-router-http/tests/adapter.spec.ts +174 -0
- package/plugins/acryl-router-http/tests/stream.spec.ts +133 -0
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +101 -0
- package/plugins/acryl-router-http/upstream.json +22 -0
- package/plugins/acryl-state/LICENSE +21 -0
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +12 -0
- package/plugins/acryl-state/src/cell.ts +67 -0
- package/plugins/acryl-state/src/index.ts +3 -0
- package/plugins/acryl-state/src/react.ts +11 -0
- package/plugins/acryl-state/src/service.ts +119 -0
- package/plugins/acryl-state/tests/cell.spec.ts +71 -0
- package/plugins/acryl-state/tests/react.spec.ts +39 -0
- package/plugins/acryl-state/tests/service.spec.ts +132 -0
- package/plugins/acryl-state/upstream.json +22 -0
- package/plugins/builder/script.ts +6 -10
- package/plugins/extensions/index.ts +20 -3
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +12 -6
- package/plugins/tasks/index.ts +14 -0
- package/plugins/tsconfig.json +14 -5
- package/templates/agent/blend.yaml +5 -7
- package/templates/agent/src/app.ts +8 -8
- package/templates/agent/src/routes.ts +39 -38
- package/templates/agent/template.json +3 -3
- package/templates/agent/test/builder.test.ts +22 -14
- package/templates/agent/test/model.test.ts +3 -3
- package/templates/agent/tsconfig.json +8 -5
- package/templates/{app → blank}/blend.yaml +3 -3
- package/templates/{app → blank}/src/app.ts +5 -5
- package/templates/blank/src/routes.ts +16 -0
- package/templates/blank/template.json +1 -0
- package/templates/{app → blank}/test/app.test.ts +1 -1
- package/templates/{app → blank}/tsconfig.json +2 -4
- package/templates/todo/README.md +27 -0
- package/templates/todo/blend.yaml +25 -0
- package/templates/todo/gitignore +2 -0
- package/templates/todo/package.json +23 -0
- package/templates/todo/src/app.ts +71 -0
- package/templates/todo/src/main.ts +19 -0
- package/templates/todo/src/page.ts +28 -0
- package/templates/todo/src/todo.ts +123 -0
- package/templates/todo/template.json +1 -0
- package/templates/todo/test/todo.test.ts +145 -0
- package/templates/todo/tsconfig.json +17 -0
- package/templates/todo/upstream.json +12 -0
- package/plugins/events/index.ts +0 -42
- package/plugins/events/plugin.json +0 -9
- package/plugins/http/index.ts +0 -64
- package/plugins/http/plugin.json +0 -10
- package/plugins/server/index.ts +0 -138
- package/plugins/server/plugin.json +0 -10
- package/templates/app/src/routes.ts +0 -14
- package/templates/app/template.json +0 -1
- /package/templates/{app → blank}/README.md +0 -0
- /package/templates/{app → blank}/gitignore +0 -0
- /package/templates/{app → blank}/package.json +0 -0
- /package/templates/{app → blank}/src/main.ts +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Cookie utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { tryDecodeURIComponent } from './url.ts'
|
|
7
|
+
import type { BufferSource } from './types.ts'
|
|
8
|
+
|
|
9
|
+
export type Cookie = Record<string, string>
|
|
10
|
+
export type SignedCookie = Record<string, string | false>
|
|
11
|
+
|
|
12
|
+
type PartitionedCookieConstraint =
|
|
13
|
+
| { partitioned: true; secure: true }
|
|
14
|
+
| { partitioned?: boolean; secure?: boolean } // reset to default
|
|
15
|
+
type SecureCookieConstraint = { secure: true }
|
|
16
|
+
type HostCookieConstraint = { secure: true; path: '/'; domain?: undefined }
|
|
17
|
+
|
|
18
|
+
export type CookieOptions = {
|
|
19
|
+
domain?: string
|
|
20
|
+
expires?: Date
|
|
21
|
+
httpOnly?: boolean
|
|
22
|
+
maxAge?: number
|
|
23
|
+
path?: string
|
|
24
|
+
secure?: boolean
|
|
25
|
+
sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none'
|
|
26
|
+
partitioned?: boolean
|
|
27
|
+
priority?: 'Low' | 'Medium' | 'High' | 'low' | 'medium' | 'high'
|
|
28
|
+
prefix?: CookiePrefixOptions
|
|
29
|
+
} & PartitionedCookieConstraint
|
|
30
|
+
export type CookiePrefixOptions = 'host' | 'secure'
|
|
31
|
+
|
|
32
|
+
export type CookieConstraint<Name> = Name extends `__Secure-${string}`
|
|
33
|
+
? CookieOptions & SecureCookieConstraint
|
|
34
|
+
: Name extends `__Host-${string}`
|
|
35
|
+
? CookieOptions & HostCookieConstraint
|
|
36
|
+
: CookieOptions
|
|
37
|
+
|
|
38
|
+
const algorithm = { name: 'HMAC', hash: 'SHA-256' }
|
|
39
|
+
|
|
40
|
+
const getCryptoKey = async (secret: string | BufferSource): Promise<CryptoKey> => {
|
|
41
|
+
const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret
|
|
42
|
+
return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify'])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const makeSignature = async (value: string, secret: string | BufferSource): Promise<string> => {
|
|
46
|
+
const key = await getCryptoKey(secret)
|
|
47
|
+
const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))
|
|
48
|
+
// the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs
|
|
49
|
+
return btoa(String.fromCharCode(...new Uint8Array(signature)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const verifySignature = async (
|
|
53
|
+
base64Signature: string,
|
|
54
|
+
value: string,
|
|
55
|
+
secret: CryptoKey
|
|
56
|
+
): Promise<boolean> => {
|
|
57
|
+
try {
|
|
58
|
+
const signatureBinStr = atob(base64Signature)
|
|
59
|
+
const signature = new Uint8Array(signatureBinStr.length)
|
|
60
|
+
for (let i = 0, len = signatureBinStr.length; i < len; i++) {
|
|
61
|
+
signature[i] = signatureBinStr.charCodeAt(i)
|
|
62
|
+
}
|
|
63
|
+
return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value))
|
|
64
|
+
} catch {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// all alphanumeric chars and all of _!#$%&'*.^`|~+-
|
|
70
|
+
// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)
|
|
71
|
+
// used only when producing cookies via serialize(); parsing uses the relaxed regex below
|
|
72
|
+
const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/
|
|
73
|
+
|
|
74
|
+
// all ASCII chars 33-126 except 34, 59, 61, and 92 (i.e. "!" to "~" but not double quote, semicolon, equals, or backslash)
|
|
75
|
+
// the strict RFC 6265 token rule above is a requirement for cookie producers; when consuming
|
|
76
|
+
// the Cookie header we accept the names other producers emit in the wild, e.g. "paraglide:lang"
|
|
77
|
+
// (see: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.6)
|
|
78
|
+
const relaxedCookieNameRegEx = /^[!#-:<>-[\]-~]+$/
|
|
79
|
+
|
|
80
|
+
// all ASCII chars 32-126 except 34, 59, and 92 (i.e. space to tilde but not double quote, semicolon, or backslash)
|
|
81
|
+
// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)
|
|
82
|
+
//
|
|
83
|
+
// note: the spec also prohibits comma and space, but we allow both since they are very common in the real world
|
|
84
|
+
// (see: https://github.com/golang/go/issues/7243)
|
|
85
|
+
const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/
|
|
86
|
+
|
|
87
|
+
const trimCookieWhitespace = (value: string): string => {
|
|
88
|
+
let start = 0
|
|
89
|
+
let end = value.length
|
|
90
|
+
|
|
91
|
+
while (start < end) {
|
|
92
|
+
const charCode = value.charCodeAt(start)
|
|
93
|
+
if (charCode !== 0x20 && charCode !== 0x09) {
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
start++
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
while (end > start) {
|
|
100
|
+
const charCode = value.charCodeAt(end - 1)
|
|
101
|
+
if (charCode !== 0x20 && charCode !== 0x09) {
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
end--
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return start === 0 && end === value.length ? value : value.slice(start, end)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const parse = (cookie: string, name?: string): Cookie => {
|
|
111
|
+
if (name && cookie.indexOf(name) === -1) {
|
|
112
|
+
// Fast-path: return immediately if the demanded-key is not in the cookie string
|
|
113
|
+
return {}
|
|
114
|
+
}
|
|
115
|
+
const pairs = cookie.split(';')
|
|
116
|
+
const parsedCookie: Cookie = Object.create(null)
|
|
117
|
+
for (const pairStr of pairs) {
|
|
118
|
+
const valueStartPos = pairStr.indexOf('=')
|
|
119
|
+
if (valueStartPos === -1) {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const cookieName = trimCookieWhitespace(pairStr.substring(0, valueStartPos))
|
|
124
|
+
if (
|
|
125
|
+
(name && name !== cookieName) ||
|
|
126
|
+
!relaxedCookieNameRegEx.test(cookieName) ||
|
|
127
|
+
cookieName in parsedCookie
|
|
128
|
+
) {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let cookieValue = trimCookieWhitespace(pairStr.substring(valueStartPos + 1))
|
|
133
|
+
if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
|
|
134
|
+
cookieValue = cookieValue.slice(1, -1)
|
|
135
|
+
}
|
|
136
|
+
if (validCookieValueRegEx.test(cookieValue)) {
|
|
137
|
+
parsedCookie[cookieName] = tryDecodeURIComponent(cookieValue)
|
|
138
|
+
if (name) {
|
|
139
|
+
// Fast-path: return only the demanded-key immediately. Other keys are not needed.
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return parsedCookie
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const parseSigned = async (
|
|
148
|
+
cookie: string,
|
|
149
|
+
secret: string | BufferSource,
|
|
150
|
+
name?: string
|
|
151
|
+
): Promise<SignedCookie> => {
|
|
152
|
+
const parsedCookie: SignedCookie = Object.create(null)
|
|
153
|
+
const secretKey = await getCryptoKey(secret)
|
|
154
|
+
|
|
155
|
+
for (const [key, value] of Object.entries(parse(cookie, name))) {
|
|
156
|
+
const signatureStartPos = value.lastIndexOf('.')
|
|
157
|
+
if (signatureStartPos < 0) {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const signedValue = value.substring(0, signatureStartPos)
|
|
162
|
+
const signature = value.substring(signatureStartPos + 1)
|
|
163
|
+
if (signature.length !== 44 || !signature.endsWith('=')) {
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const isVerified = await verifySignature(signature, signedValue, secretKey)
|
|
168
|
+
parsedCookie[key] = isVerified ? signedValue : false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return parsedCookie
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const _serialize = (name: string, value: string, opt: CookieOptions = {}): string => {
|
|
175
|
+
if (!validCookieNameRegEx.test(name)) {
|
|
176
|
+
throw new Error('Invalid cookie name')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let cookie = `${name}=${value}`
|
|
180
|
+
|
|
181
|
+
if (name.startsWith('__Secure-') && !opt.secure) {
|
|
182
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.1
|
|
183
|
+
throw new Error('__Secure- Cookie must have Secure attributes')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (name.startsWith('__Host-')) {
|
|
187
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.2
|
|
188
|
+
if (!opt.secure) {
|
|
189
|
+
throw new Error('__Host- Cookie must have Secure attributes')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (opt.path !== '/') {
|
|
193
|
+
throw new Error('__Host- Cookie must have Path attributes with "/"')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (opt.domain) {
|
|
197
|
+
throw new Error('__Host- Cookie must not have Domain attributes')
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const key of ['domain', 'path', 'sameSite', 'priority'] as (keyof CookieOptions)[]) {
|
|
202
|
+
if (opt[key] && /[;\r\n]/.test(opt[key] as string)) {
|
|
203
|
+
throw new Error(`${key} must not contain ";", "\\r", or "\\n"`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (opt && typeof opt.maxAge === 'number' && opt.maxAge >= 0) {
|
|
208
|
+
if (opt.maxAge > 34560000) {
|
|
209
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.6.2
|
|
210
|
+
throw new Error(
|
|
211
|
+
'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.'
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
cookie += `; Max-Age=${opt.maxAge | 0}`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (opt.domain && opt.prefix !== 'host') {
|
|
218
|
+
cookie += `; Domain=${opt.domain}`
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (opt.path) {
|
|
222
|
+
cookie += `; Path=${opt.path}`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (opt.expires) {
|
|
226
|
+
if (opt.expires.getTime() - Date.now() > 34560000_000) {
|
|
227
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.5
|
|
228
|
+
throw new Error(
|
|
229
|
+
'Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.'
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
cookie += `; Expires=${opt.expires.toUTCString()}`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (opt.httpOnly) {
|
|
236
|
+
cookie += '; HttpOnly'
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (opt.secure) {
|
|
240
|
+
cookie += '; Secure'
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (opt.sameSite) {
|
|
244
|
+
cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (opt.priority) {
|
|
248
|
+
cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (opt.partitioned) {
|
|
252
|
+
// https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html#section-2.1
|
|
253
|
+
if (!opt.secure) {
|
|
254
|
+
throw new Error('Partitioned Cookie must have Secure attributes')
|
|
255
|
+
}
|
|
256
|
+
cookie += '; Partitioned'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return cookie
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export const serialize = <Name extends string>(
|
|
263
|
+
name: Name,
|
|
264
|
+
value: string,
|
|
265
|
+
opt?: CookieConstraint<Name>
|
|
266
|
+
): string => {
|
|
267
|
+
value = encodeURIComponent(value)
|
|
268
|
+
return _serialize(name, value, opt)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const serializeSigned = async (
|
|
272
|
+
name: string,
|
|
273
|
+
value: string,
|
|
274
|
+
secret: string | BufferSource,
|
|
275
|
+
opt: CookieOptions = {}
|
|
276
|
+
): Promise<string> => {
|
|
277
|
+
const signature = await makeSignature(value, secret)
|
|
278
|
+
value = `${value}.${signature}`
|
|
279
|
+
value = encodeURIComponent(value)
|
|
280
|
+
return _serialize(name, value, opt)
|
|
281
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { md5, sha1, sha256 } from './crypto.ts'
|
|
3
|
+
|
|
4
|
+
describe('crypto', () => {
|
|
5
|
+
it('sha256', async () => {
|
|
6
|
+
expect(await sha256('acryl')).toBe(
|
|
7
|
+
'1722bce316636434b7346179cda6e5918d1265e875548d8393d897d0e8056490'
|
|
8
|
+
)
|
|
9
|
+
expect(await sha256('炎')).toBe(
|
|
10
|
+
'1fddc5a562ee1fbeb4fc6def7d4be4911fcdae4273b02ae3a507b170ba0ea169'
|
|
11
|
+
)
|
|
12
|
+
expect(await sha256('abcdedf')).not.toBe('abcdef')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('sha1', async () => {
|
|
16
|
+
expect(await sha1('acryl')).toBe('a0d04d622e8eee4b96ec3e768158f79849e4feaa')
|
|
17
|
+
expect(await sha1('炎')).toBe('d56e09ae2421b2b8a0b5ee5fdceaed663c8c9472')
|
|
18
|
+
expect(await sha1('abcdedf')).not.toBe('abcdef')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// MD5 is not part of the WebCrypto standard.
|
|
22
|
+
// Node.js' Web Crypto API does not support it (But Cloudflare Workers supports it).
|
|
23
|
+
// We should skip this test in a Node.js environment.
|
|
24
|
+
it.skip('md5', async () => {
|
|
25
|
+
expect(await md5('acryl')).toBe('6d6de29f8c2b6b8741b0d549b556de5c')
|
|
26
|
+
expect(await md5('炎')).toBe('f620d89a5a782c22b4420acb39121be3')
|
|
27
|
+
expect(await md5('abcdedf')).not.toBe('abcdef')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('Should not be the same values - compare difference objects', async () => {
|
|
31
|
+
expect(await sha256({ foo: 'bar' })).not.toEqual(
|
|
32
|
+
await sha256({
|
|
33
|
+
bar: 'foo',
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('Should create hash for Buffer', async () => {
|
|
39
|
+
const hash = createHash('sha256').update(new Uint8Array(1)).digest('hex')
|
|
40
|
+
expect(await sha256(new Uint8Array(1))).toBe(hash)
|
|
41
|
+
expect(await sha256(new Uint8Array(1))).not.toEqual(await sha256(new Uint8Array(2)))
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Crypto utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JSONValue } from './types.ts'
|
|
7
|
+
|
|
8
|
+
type Algorithm = {
|
|
9
|
+
name: string
|
|
10
|
+
alias: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Data = string | boolean | number | JSONValue | ArrayBufferView | ArrayBuffer
|
|
14
|
+
|
|
15
|
+
export const sha256 = async (data: Data): Promise<string | null> => {
|
|
16
|
+
const algorithm: Algorithm = { name: 'SHA-256', alias: 'sha256' }
|
|
17
|
+
const hash = await createHash(data, algorithm)
|
|
18
|
+
return hash
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const sha1 = async (data: Data): Promise<string | null> => {
|
|
22
|
+
const algorithm: Algorithm = { name: 'SHA-1', alias: 'sha1' }
|
|
23
|
+
const hash = await createHash(data, algorithm)
|
|
24
|
+
return hash
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const md5 = async (data: Data): Promise<string | null> => {
|
|
28
|
+
const algorithm: Algorithm = { name: 'MD5', alias: 'md5' }
|
|
29
|
+
const hash = await createHash(data, algorithm)
|
|
30
|
+
return hash
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const createHash = async (data: Data, algorithm: Algorithm): Promise<string | null> => {
|
|
34
|
+
let sourceBuffer: ArrayBufferView | ArrayBuffer
|
|
35
|
+
|
|
36
|
+
if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
|
|
37
|
+
sourceBuffer = data
|
|
38
|
+
} else {
|
|
39
|
+
if (typeof data === 'object') {
|
|
40
|
+
data = JSON.stringify(data)
|
|
41
|
+
}
|
|
42
|
+
sourceBuffer = new TextEncoder().encode(String(data))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (crypto && crypto.subtle) {
|
|
46
|
+
const buffer = await crypto.subtle.digest(
|
|
47
|
+
{
|
|
48
|
+
name: algorithm.name,
|
|
49
|
+
},
|
|
50
|
+
sourceBuffer as ArrayBuffer
|
|
51
|
+
)
|
|
52
|
+
const hash = Array.prototype.map
|
|
53
|
+
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
|
|
54
|
+
.join('')
|
|
55
|
+
return hash
|
|
56
|
+
}
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { decodeBase64Url, encodeBase64Url } from './encode.ts'
|
|
2
|
+
|
|
3
|
+
const toURLBase64 = (base64String: string): string =>
|
|
4
|
+
base64String.replace(/\+|\//g, (m) => ({ '+': '-', '/': '_' })[m] ?? m)
|
|
5
|
+
|
|
6
|
+
const str2UInt8Array = (s: string): Uint8Array<ArrayBuffer> => {
|
|
7
|
+
const buffer = new Uint8Array<ArrayBuffer>(new ArrayBuffer(s.length))
|
|
8
|
+
for (let i = 0, len = buffer.byteLength; i < len; i++) {
|
|
9
|
+
buffer[i] = s.charCodeAt(i)
|
|
10
|
+
}
|
|
11
|
+
return buffer
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('base64', () => {
|
|
15
|
+
const utf8Encoder = new TextEncoder()
|
|
16
|
+
describe.each([
|
|
17
|
+
// basic
|
|
18
|
+
[utf8Encoder.encode('Hello, 世界'), 'SGVsbG8sIOS4lueVjA=='],
|
|
19
|
+
[utf8Encoder.encode('炎'), '54KO'],
|
|
20
|
+
[utf8Encoder.encode('🔥'), '8J+UpQ=='],
|
|
21
|
+
[
|
|
22
|
+
utf8Encoder.encode('http://github.com/acryldev/acryl'),
|
|
23
|
+
'aHR0cDovL2dpdGh1Yi5jb20vYWNyeWxkZXYvYWNyeWw=',
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
// RFC 3548 examples
|
|
27
|
+
[str2UInt8Array('\x14\xfb\x9c\x03\xd9\x7e'), 'FPucA9l+'],
|
|
28
|
+
[str2UInt8Array('\x14\xfb\x9c\x03\xd9'), 'FPucA9k='],
|
|
29
|
+
[str2UInt8Array('\x14\xfb\x9c\x03'), 'FPucAw=='],
|
|
30
|
+
|
|
31
|
+
// RFC 4648 examples
|
|
32
|
+
[str2UInt8Array(''), ''],
|
|
33
|
+
[str2UInt8Array('f'), 'Zg=='],
|
|
34
|
+
[str2UInt8Array('fo'), 'Zm8='],
|
|
35
|
+
[str2UInt8Array('foo'), 'Zm9v'],
|
|
36
|
+
[str2UInt8Array('foob'), 'Zm9vYg=='],
|
|
37
|
+
[str2UInt8Array('fooba'), 'Zm9vYmE='],
|
|
38
|
+
[str2UInt8Array('foobar'), 'Zm9vYmFy'],
|
|
39
|
+
|
|
40
|
+
// Wikipedia examples
|
|
41
|
+
[str2UInt8Array('sure.'), 'c3VyZS4='],
|
|
42
|
+
[str2UInt8Array('sure'), 'c3VyZQ=='],
|
|
43
|
+
[str2UInt8Array('sur'), 'c3Vy'],
|
|
44
|
+
[str2UInt8Array('su'), 'c3U='],
|
|
45
|
+
[str2UInt8Array('leasure.'), 'bGVhc3VyZS4='],
|
|
46
|
+
[str2UInt8Array('easure.'), 'ZWFzdXJlLg=='],
|
|
47
|
+
[str2UInt8Array('asure.'), 'YXN1cmUu'],
|
|
48
|
+
[str2UInt8Array('sure.'), 'c3VyZS4='],
|
|
49
|
+
])('%s, %s', (stdDecoded, stdEncoded) => {
|
|
50
|
+
it('encode', () => {
|
|
51
|
+
const got = encodeBase64Url(stdDecoded.buffer)
|
|
52
|
+
const want = toURLBase64(stdEncoded)
|
|
53
|
+
expect(got).toStrictEqual(want)
|
|
54
|
+
})
|
|
55
|
+
it('decode', () => {
|
|
56
|
+
const got = decodeBase64Url(toURLBase64(stdEncoded))
|
|
57
|
+
const want = stdDecoded
|
|
58
|
+
expect(got).toStrictEqual(want)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Encode utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const decodeBase64Url = (str: string): Uint8Array<ArrayBuffer> => {
|
|
7
|
+
return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' })[m] ?? m))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const encodeBase64Url = (buf: ArrayBufferLike): string =>
|
|
11
|
+
encodeBase64(buf).replace(/\/|\+/g, (m) => ({ '/': '_', '+': '-' })[m] ?? m)
|
|
12
|
+
|
|
13
|
+
// This approach is written in MDN.
|
|
14
|
+
// btoa does not support utf-8 characters. So we need a little bit hack.
|
|
15
|
+
export const encodeBase64 = (buf: ArrayBufferLike): string => {
|
|
16
|
+
let binary = ''
|
|
17
|
+
const bytes = new Uint8Array(buf)
|
|
18
|
+
for (let i = 0, len = bytes.length; i < len; i++) {
|
|
19
|
+
binary += String.fromCharCode(bytes[i])
|
|
20
|
+
}
|
|
21
|
+
return btoa(binary)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// atob does not support utf-8 characters. So we need a little bit hack.
|
|
25
|
+
export const decodeBase64 = (str: string): Uint8Array<ArrayBuffer> => {
|
|
26
|
+
const binary = atob(str)
|
|
27
|
+
const bytes = new Uint8Array<ArrayBuffer>(new ArrayBuffer(binary.length))
|
|
28
|
+
const half = binary.length / 2
|
|
29
|
+
for (let i = 0, j = binary.length - 1; i <= half; i++, j--) {
|
|
30
|
+
bytes[i] = binary.charCodeAt(i)
|
|
31
|
+
bytes[j] = binary.charCodeAt(j)
|
|
32
|
+
}
|
|
33
|
+
return bytes
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getFilePath, getFilePathWithoutDefaultDocument } from './filepath.ts'
|
|
2
|
+
|
|
3
|
+
describe('getFilePathWithoutDefaultDocument', () => {
|
|
4
|
+
it('Should return file path correctly', async () => {
|
|
5
|
+
expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt' })).toBe('foo.txt')
|
|
6
|
+
expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt', root: 'bar' })).toBe(
|
|
7
|
+
'bar/foo.txt'
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
expect(getFilePathWithoutDefaultDocument({ filename: '../foo' })).toBeUndefined()
|
|
11
|
+
expect(getFilePathWithoutDefaultDocument({ filename: '/../foo' })).toBeUndefined()
|
|
12
|
+
expect(getFilePathWithoutDefaultDocument({ filename: './../foo' })).toBeUndefined()
|
|
13
|
+
expect(getFilePathWithoutDefaultDocument({ filename: 'foo..bar.txt' })).toBe('foo..bar.txt')
|
|
14
|
+
expect(getFilePathWithoutDefaultDocument({ filename: '/foo..bar.txt' })).toBe('foo..bar.txt')
|
|
15
|
+
expect(getFilePathWithoutDefaultDocument({ filename: './foo..bar.txt' })).toBe('foo..bar.txt')
|
|
16
|
+
expect(getFilePathWithoutDefaultDocument({ filename: './..foo/bar.txt' })).toBe('..foo/bar.txt')
|
|
17
|
+
expect(getFilePathWithoutDefaultDocument({ filename: './foo../bar.txt' })).toBe('foo../bar.txt')
|
|
18
|
+
expect(getFilePathWithoutDefaultDocument({ filename: './..foo../bar.txt' })).toBe(
|
|
19
|
+
'..foo../bar.txt'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
expect(
|
|
23
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/../foo') })
|
|
24
|
+
).toBeUndefined()
|
|
25
|
+
expect(
|
|
26
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./../foo') })
|
|
27
|
+
).toBeUndefined()
|
|
28
|
+
expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('foo..bar.txt') })).toBe(
|
|
29
|
+
'foo..bar.txt'
|
|
30
|
+
)
|
|
31
|
+
expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/foo..bar.txt') })).toBe(
|
|
32
|
+
'foo..bar.txt'
|
|
33
|
+
)
|
|
34
|
+
expect(
|
|
35
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo..bar.txt') })
|
|
36
|
+
).toBe('foo..bar.txt')
|
|
37
|
+
expect(
|
|
38
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo/bar.txt') })
|
|
39
|
+
).toBe('..foo/bar.txt')
|
|
40
|
+
expect(
|
|
41
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo../bar.txt') })
|
|
42
|
+
).toBe('foo../bar.txt')
|
|
43
|
+
expect(
|
|
44
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo../bar.txt') })
|
|
45
|
+
).toBe('..foo../bar.txt')
|
|
46
|
+
|
|
47
|
+
// Multiple path segments: every backslash must be normalized, not just the first
|
|
48
|
+
expect(
|
|
49
|
+
getFilePathWithoutDefaultDocument({ filename: slashToBackslash('foo/bar/baz.txt') })
|
|
50
|
+
).toBe('foo/bar/baz.txt')
|
|
51
|
+
expect(
|
|
52
|
+
getFilePathWithoutDefaultDocument({
|
|
53
|
+
filename: slashToBackslash('foo/bar/baz.txt'),
|
|
54
|
+
root: 'assets',
|
|
55
|
+
})
|
|
56
|
+
).toBe('assets/foo/bar/baz.txt')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('getFilePath', () => {
|
|
61
|
+
it('Should return file path correctly', async () => {
|
|
62
|
+
expect(getFilePath({ filename: 'foo' })).toBe('foo/index.html')
|
|
63
|
+
|
|
64
|
+
expect(getFilePath({ filename: 'foo', root: 'bar' })).toBe('bar/foo/index.html')
|
|
65
|
+
|
|
66
|
+
expect(getFilePath({ filename: 'foo', defaultDocument: 'index.txt' })).toBe('foo/index.txt')
|
|
67
|
+
expect(getFilePath({ filename: 'foo', root: 'bar', defaultDocument: 'index.txt' })).toBe(
|
|
68
|
+
'bar/foo/index.txt'
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
expect(getFilePath({ filename: 'filename.suffix_index' })).toBe('filename.suffix_index')
|
|
72
|
+
expect(getFilePath({ filename: 'filename.suffix-index' })).toBe('filename.suffix-index')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
function slashToBackslash(filename: string) {
|
|
77
|
+
return filename.split('/').join('\\')
|
|
78
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* FilePath utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type FilePathOptions = {
|
|
7
|
+
filename: string
|
|
8
|
+
root?: string
|
|
9
|
+
defaultDocument?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getFilePath = (options: FilePathOptions): string | undefined => {
|
|
13
|
+
let filename = options.filename
|
|
14
|
+
const defaultDocument = options.defaultDocument || 'index.html'
|
|
15
|
+
|
|
16
|
+
if (filename.endsWith('/')) {
|
|
17
|
+
// /top/ => /top/index.html
|
|
18
|
+
filename = filename.concat(defaultDocument)
|
|
19
|
+
} else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
|
|
20
|
+
// /top => /top/index.html
|
|
21
|
+
filename = filename.concat('/' + defaultDocument)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const path = getFilePathWithoutDefaultDocument({
|
|
25
|
+
root: options.root,
|
|
26
|
+
filename,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return path
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const getFilePathWithoutDefaultDocument = (
|
|
33
|
+
options: Omit<FilePathOptions, 'defaultDocument'>
|
|
34
|
+
): string | undefined => {
|
|
35
|
+
let root = options.root || ''
|
|
36
|
+
let filename = options.filename
|
|
37
|
+
|
|
38
|
+
if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// /foo.html => foo.html
|
|
43
|
+
filename = filename.replace(/^\.?[\/\\]/, '')
|
|
44
|
+
|
|
45
|
+
// foo\bar.txt => foo/bar.txt
|
|
46
|
+
filename = filename.replace(/\\/g, '/')
|
|
47
|
+
|
|
48
|
+
// assets/ => assets
|
|
49
|
+
root = root.replace(/\/$/, '')
|
|
50
|
+
|
|
51
|
+
// ./assets/foo.html => assets/foo.html
|
|
52
|
+
let path = root ? root + '/' + filename : filename
|
|
53
|
+
path = path.replace(/^\.?\//, '')
|
|
54
|
+
|
|
55
|
+
if (root[0] !== '/' && path[0] === '/') {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return path
|
|
60
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Handler utility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { COMPOSED_HANDLER } from './constants.ts'
|
|
7
|
+
|
|
8
|
+
export const isMiddleware = (handler: Function) => handler.length > 1
|
|
9
|
+
export const findTargetHandler = (handler: Function): Function => {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
return (handler as any)[COMPOSED_HANDLER]
|
|
12
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
findTargetHandler((handler as any)[COMPOSED_HANDLER])
|
|
14
|
+
: handler
|
|
15
|
+
}
|