akanjs 3.1.1 → 3.1.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +22 -0
- package/README.md +22 -0
- package/client/cookie.ts +23 -7
- package/client/csrTypes.ts +1 -1
- package/client/makePageProto.tsx +10 -1
- package/client/router.ts +57 -16
- package/common/IpLiteral.ts +4 -2
- package/common/TrustedProxy.ts +45 -2
- package/common/akanNames.ts +169 -0
- package/common/codeAgentProfile.ts +51 -4
- package/common/index.ts +13 -0
- package/common/localeConfig.ts +30 -5
- package/common/localePath.ts +26 -0
- package/common/markdownSpans.ts +3 -1
- package/common/mcpExposure.ts +7 -7
- package/common/websocketContract.ts +50 -0
- package/constant/index.ts +1 -0
- package/constant/inputParser.ts +10 -2
- package/constant/requestValue.ts +47 -0
- package/dictionary/agent.dictionary.ts +2 -2
- package/dictionary/base.dictionary.ts +4 -0
- package/dictionary/dictInfo.ts +9 -9
- package/document/database.ts +1 -0
- package/document/documentQuery.ts +42 -6
- package/document/filterMeta.ts +56 -8
- package/document/queryEvaluator.ts +13 -8
- package/fetch/client/fetchClient.ts +28 -62
- package/fetch/client/httpClient.ts +1 -0
- package/fetch/client/sliceInitHandle.ts +18 -18
- package/fetch/index.ts +1 -0
- package/fetch/sessionCookie.ts +37 -0
- package/index.ts +8 -0
- package/package.json +13 -27
- package/server/akanApp.ts +108 -29
- package/server/akanServer.ts +18 -7
- package/server/assetHeaders.ts +20 -1
- package/server/cachePolicy.ts +18 -5
- package/server/devtools/depsSerializer.ts +2 -1
- package/server/devtools/devtoolsRouter.ts +3 -2
- package/server/devtools/signalSerializer.ts +5 -5
- package/server/hmr/clientReport.ts +10 -5
- package/server/imageOptimizer.ts +117 -22
- package/server/lifecycle/buildEnv.ts +11 -0
- package/server/lifecycle/loadBuildEnv.ts +3 -0
- package/server/logging/logControlSocket.ts +20 -14
- package/server/logging/logStreamRoute.ts +12 -8
- package/server/logging/rotatingLogWriter.ts +15 -1
- package/server/machine/machineToken.ts +4 -0
- package/server/mcp/McpAuth.ts +12 -4
- package/server/mcp/McpExecutionContext.ts +4 -0
- package/server/mcp/McpRouter.ts +25 -13
- package/server/mcp/PagePromptComposer.ts +4 -7
- package/server/oauth/OAuthClientIdMetadata.ts +29 -6
- package/server/oauth/OAuthRegistration.ts +9 -0
- package/server/ops/failureLimiter.ts +42 -0
- package/server/ops/opsCommand.ts +19 -7
- package/server/ops/opsRoute.ts +60 -9
- package/server/ops/opsToken.ts +5 -0
- package/server/ops/snapshotJobs.ts +3 -1
- package/server/ops/snapshotRestore.ts +23 -1
- package/server/ops/sqliteSnapshot.ts +98 -18
- package/server/ops/statusAccess.ts +37 -19
- package/server/proxy/hostBasePathWebProxy.ts +4 -7
- package/server/proxy/localeWebProxy.ts +36 -16
- package/server/resolver/database.resolver.ts +15 -11
- package/server/resolver/service.resolver.ts +17 -2
- package/server/resolver/signal.resolver.ts +50 -40
- package/server/routeElementComposer.tsx +42 -9
- package/server/routeShellGate.ts +42 -0
- package/server/routing/apiRouter.ts +64 -1
- package/server/routing/serverLimits.ts +151 -0
- package/server/routing/soloAppRoutes.ts +23 -12
- package/server/rscClient.tsx +5 -3
- package/server/rscScrollRestoration.ts +60 -9
- package/server/rscWorker.tsx +76 -24
- package/server/sitemap.ts +5 -7
- package/server/ssrFromRscRenderer.tsx +36 -4
- package/server/ssrTypes.ts +5 -2
- package/server/staticPath.ts +36 -1
- package/server/systemPageDocument.tsx +5 -3
- package/server/systemPages.tsx +14 -8
- package/server/tunnel/TunnelAgent.ts +19 -2
- package/server/tunnel/TunnelDataSocket.ts +4 -2
- package/server/tunnel/TunnelHttpStream.ts +7 -2
- package/server/tunnel/TunnelTcpStream.ts +6 -3
- package/server/tunnel/TunnelWebsocketStream.ts +9 -2
- package/server/tunnel/tunnelStream.ts +6 -0
- package/server/types.tsx +20 -2
- package/server/webRouter.ts +38 -18
- package/service/agent.service.ts +9 -7
- package/service/injectInfo.ts +2 -1
- package/service/liveSyncHub.ts +6 -0
- package/service/predefinedAdaptor/anthropicLlm.ts +13 -12
- package/service/predefinedAdaptor/database.adaptor.ts +4 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +3 -0
- package/service/predefinedAdaptor/llmOverflow.ts +5 -1
- package/service/predefinedAdaptor/openaiDialect.ts +9 -8
- package/service/predefinedAdaptor/searchIndex.ts +21 -1
- package/service/predefinedAdaptor/sql/QueryCompiler.ts +28 -4
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +45 -11
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +6 -1
- package/service/predefinedAdaptor/sql/types.ts +3 -0
- package/service/predefinedAdaptor/storage.adaptor.ts +11 -10
- package/service/serve.ts +3 -3
- package/service/serviceModule.ts +28 -24
- package/service/types.ts +3 -0
- package/signal/SignalFailure.ts +5 -2
- package/signal/agent/AgentCatalogue.ts +3 -2
- package/signal/agentMeter.ts +81 -9
- package/signal/agentRelayPolicy.ts +53 -2
- package/signal/agentTurnStream.ts +12 -1
- package/signal/guard.ts +2 -2
- package/signal/mcp/McpDocument.ts +3 -3
- package/signal/mcp/McpUriTemplate.ts +5 -5
- package/signal/middleware.ts +15 -1
- package/signal/roomKey.ts +53 -0
- package/signal/signalContext.ts +100 -40
- package/signal/slice.ts +27 -22
- package/signal/trace.ts +17 -6
- package/store/action.ts +20 -49
- package/store/actionTag.ts +2 -0
- package/store/agent/ScreenReader.ts +36 -4
- package/store/agent/ScreenTarget.ts +1 -1
- package/store/agent/StoreCatalogue.ts +7 -1
- package/store/agent/StoreSurfaceSource.ts +5 -0
- package/store/agentic/AgentConfirm.ts +30 -0
- package/store/agentic/StStateBuilder.ts +2 -2
- package/store/agentic/StToolBuilder.ts +7 -2
- package/store/agentic/formFields.ts +1 -2
- package/store/agentic/index.ts +1 -0
- package/store/agentic/useFieldTool.ts +4 -3
- package/store/deprecatedNames.ts +6 -0
- package/store/draftStore.ts +2 -2
- package/store/formUploadOf.ts +6 -5
- package/store/index.ts +1 -2
- package/store/sliceKeys.ts +9 -32
- package/store/state.ts +1 -1
- package/store/stateBuilder.ts +1 -1
- package/store/store.ts +1 -1
- package/store/storeInstance.ts +12 -8
- package/store/storeRegistry.ts +14 -2
- package/types/client/cookie.d.ts +7 -2
- package/types/client/csrTypes.d.ts +1 -1
- package/types/client/router.d.ts +4 -1
- package/types/common/akanNames.d.ts +149 -0
- package/types/common/codeAgentProfile.d.ts +4 -1
- package/types/common/index.d.ts +3 -1
- package/types/common/localeConfig.d.ts +8 -0
- package/types/common/localePath.d.ts +13 -0
- package/types/common/websocketContract.d.ts +7 -0
- package/types/constant/index.d.ts +1 -0
- package/types/constant/requestValue.d.ts +5 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +3 -3
- package/types/document/database.d.ts +1 -0
- package/types/document/documentQuery.d.ts +3 -0
- package/types/document/filterMeta.d.ts +4 -0
- package/types/fetch/client/sliceInitHandle.d.ts +1 -2
- package/types/fetch/index.d.ts +1 -0
- package/types/fetch/sessionCookie.d.ts +12 -0
- package/types/index.d.ts +8 -0
- package/types/server/akanApp.d.ts +1 -0
- package/types/server/assetHeaders.d.ts +5 -0
- package/types/server/cachePolicy.d.ts +2 -0
- package/types/server/devtools/devtoolsRouter.d.ts +1 -1
- package/types/server/imageOptimizer.d.ts +4 -1
- package/types/server/lifecycle/buildEnv.d.ts +2 -0
- package/types/server/lifecycle/loadBuildEnv.d.ts +1 -0
- package/types/server/logging/logStreamRoute.d.ts +1 -1
- package/types/server/logging/rotatingLogWriter.d.ts +3 -0
- package/types/server/machine/machineToken.d.ts +2 -0
- package/types/server/mcp/McpAuth.d.ts +2 -1
- package/types/server/mcp/McpExecutionContext.d.ts +1 -0
- package/types/server/oauth/OAuthRegistration.d.ts +2 -0
- package/types/server/ops/failureLimiter.d.ts +11 -0
- package/types/server/ops/opsRoute.d.ts +4 -2
- package/types/server/ops/opsToken.d.ts +2 -0
- package/types/server/ops/snapshotRestore.d.ts +3 -1
- package/types/server/ops/sqliteSnapshot.d.ts +7 -1
- package/types/server/ops/statusAccess.d.ts +8 -4
- package/types/server/proxy/hostBasePathWebProxy.d.ts +1 -0
- package/types/server/resolver/signal.resolver.d.ts +0 -1
- package/types/server/routeElementComposer.d.ts +10 -4
- package/types/server/routeShellGate.d.ts +12 -0
- package/types/server/routing/apiRouter.d.ts +6 -1
- package/types/server/routing/serverLimits.d.ts +23 -0
- package/types/server/routing/soloAppRoutes.d.ts +1 -1
- package/types/server/rscScrollRestoration.d.ts +1 -0
- package/types/server/ssrTypes.d.ts +5 -2
- package/types/server/staticPath.d.ts +1 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/tunnel/TunnelAgent.d.ts +6 -0
- package/types/server/tunnel/TunnelDataSocket.d.ts +2 -0
- package/types/server/tunnel/TunnelTcpStream.d.ts +1 -1
- package/types/server/tunnel/tunnelStream.d.ts +1 -0
- package/types/server/types.d.ts +11 -2
- package/types/service/agent.service.d.ts +1 -1
- package/types/service/liveSyncHub.d.ts +1 -0
- package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +1 -0
- package/types/service/predefinedAdaptor/openaiDialect.d.ts +1 -1
- package/types/service/predefinedAdaptor/searchIndex.d.ts +5 -1
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/types.d.ts +3 -0
- package/types/service/types.d.ts +3 -0
- package/types/signal/SignalFailure.d.ts +1 -1
- package/types/signal/agentMeter.d.ts +4 -1
- package/types/signal/agentRelayPolicy.d.ts +12 -0
- package/types/signal/guard.d.ts +1 -1
- package/types/signal/roomKey.d.ts +8 -0
- package/types/signal/signalContext.d.ts +20 -6
- package/types/signal/slice.d.ts +1 -0
- package/types/signal/trace.d.ts +4 -0
- package/types/store/actionTag.d.ts +2 -0
- package/types/store/agent/ScreenReader.d.ts +2 -1
- package/types/store/agent/StoreCatalogue.d.ts +5 -0
- package/types/store/agentic/AgentConfirm.d.ts +15 -0
- package/types/store/agentic/StToolBuilder.d.ts +3 -1
- package/types/store/agentic/index.d.ts +1 -0
- package/types/store/deprecatedNames.d.ts +5 -0
- package/types/store/index.d.ts +1 -2
- package/types/store/sliceKeys.d.ts +12 -2
- package/types/ui/Agent/agentSessionOf.d.ts +2 -0
- package/types/ui/Agent/useAccountBoundSession.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +1 -1
- package/types/ui/Icon.d.ts +15 -0
- package/types/ui/animated.d.ts +3 -3
- package/types/ui/sliceNamesOf.d.ts +4 -4
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +10 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +6 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +7 -0
- package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +2 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +2 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +6 -1
- package/types/vendor/use-agentic/ToolRunner.d.ts +7 -2
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +2 -1
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/ui/Agent/Chat.tsx +2 -0
- package/ui/Agent/Zone.tsx +2 -0
- package/ui/Agent/agentSessionOf.ts +54 -5
- package/ui/Agent/useAccountBoundSession.ts +26 -0
- package/ui/Agent/useScreenActions.ts +1 -1
- package/ui/BottomSheet.tsx +1 -1
- package/ui/Button.tsx +5 -5
- package/ui/Data/Item.tsx +2 -8
- package/ui/Data/ListContainer.tsx +4 -4
- package/ui/Data/QueryMaker.tsx +3 -2
- package/ui/Data/RefPicker.tsx +3 -2
- package/ui/Data/UnlistedEditModal.tsx +2 -1
- package/ui/Data/ViewActions.tsx +4 -4
- package/ui/Data/dataExport.ts +3 -0
- package/ui/Data/useViewModal.ts +10 -15
- package/ui/Dialog/LegacyModal.tsx +1 -1
- package/ui/Dialog/Provider.tsx +2 -2
- package/ui/DragAction.tsx +1 -1
- package/ui/Dropdown.tsx +2 -2
- package/ui/Field/lightRefCache.ts +2 -2
- package/ui/Field/useRelationOptions.ts +3 -10
- package/ui/Icon.tsx +87 -0
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/Sider.tsx +3 -3
- package/ui/Link/Anchor.tsx +4 -1
- package/ui/Load/Edit.tsx +2 -2
- package/ui/Load/Edit_Client.tsx +3 -3
- package/ui/Load/Pagination.tsx +3 -3
- package/ui/Load/View.tsx +15 -14
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Model/DraftBar.tsx +7 -14
- package/ui/Model/EditModal.tsx +9 -40
- package/ui/Model/EditWrapper.tsx +3 -5
- package/ui/Model/NewWrapper_Client.tsx +3 -7
- package/ui/Model/Remove.tsx +2 -2
- package/ui/Model/RemoveWrapper.tsx +2 -4
- package/ui/Model/SureToRemove.tsx +2 -2
- package/ui/Model/ViewEditModal.tsx +9 -20
- package/ui/Model/ViewModal.tsx +4 -11
- package/ui/Model/ViewWrapper.tsx +4 -6
- package/ui/ScreenNavigator.tsx +2 -2
- package/ui/Signal/WebSocket.tsx +2 -2
- package/ui/System/CSR.tsx +3 -3
- package/ui/System/Reconnect.tsx +2 -2
- package/ui/System/SSR.tsx +2 -2
- package/ui/System/SelectLanguage.tsx +1 -1
- package/ui/System/ThemeToggle.tsx +1 -1
- package/ui/Tab/Provider.tsx +1 -0
- package/ui/Toast.tsx +7 -15
- package/ui/index.ts +0 -1
- package/ui/sliceNamesOf.ts +25 -15
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +2 -1
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +10 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +4 -0
- package/vendor/@akanjs/native/native/desktop/src/acl.rs +166 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +25 -6
- package/vendor/@akanjs/native/native/desktop/src/navigation.rs +22 -10
- package/vendor/@akanjs/native/native/desktop/src/routes.rs +2 -2
- package/vendor/@akanjs/native/native/desktop/src/vectors.rs +28 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +1 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +10 -2
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +4 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +1 -0
- package/vendor/@akanjs/native/packages/cli/src/config.ts +10 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +5 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +4 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +30 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +1 -1
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +18 -2
- package/vendor/@akanjs/native/packages/core/vectors/navigation.json +22 -1
- package/vendor/@akanjs/native/packages/core/vectors/routes.json +4 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +5 -1
- package/vendor/@akanjs/native/packages/desktop/src/paths.ts +24 -1
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +4 -5
- package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +6 -8
- package/vendor/@akanjs/native/plugins/filesystem/src/desktop.ts +4 -2
- package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +22 -3
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +6 -0
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +6 -0
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +106 -18
- package/vendor/use-agentic/AgentSession.ts +7 -2
- package/vendor/use-agentic/ToolRunner.ts +44 -5
- package/vendor/use-agentic/types.ts +2 -0
- package/vendor/use-agentic/useAgentTool.ts +4 -2
- package/webkit/McpAppAgent.ts +2 -1
- package/webkit/bootCsr.tsx +1 -1
- package/webkit/useCsrValues.ts +5 -1
- package/webkit/usePageTool.tsx +1 -0
- package/store/databaseStateNames.ts +0 -24
- package/store/formSetterNames.ts +0 -18
- package/types/store/databaseStateNames.d.ts +0 -18
- package/types/store/formSetterNames.d.ts +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2-rc.0",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -206,6 +206,11 @@
|
|
|
206
206
|
"import": "./server/lifecycle/portInUse.ts",
|
|
207
207
|
"default": "./server/lifecycle/portInUse.ts"
|
|
208
208
|
},
|
|
209
|
+
"./server/lifecycle/buildEnv": {
|
|
210
|
+
"types": "./types/server/lifecycle/buildEnv.d.ts",
|
|
211
|
+
"import": "./server/lifecycle/buildEnv.ts",
|
|
212
|
+
"default": "./server/lifecycle/buildEnv.ts"
|
|
213
|
+
},
|
|
209
214
|
"./server/artifact/routeSeedIndexStore": {
|
|
210
215
|
"types": "./types/server/artifact/routeSeedIndexStore.d.ts",
|
|
211
216
|
"import": "./server/artifact/routeSeedIndexStore.ts",
|
|
@@ -250,32 +255,31 @@
|
|
|
250
255
|
"dependencies": {
|
|
251
256
|
"@lexical/plain-text": "^0.52.0",
|
|
252
257
|
"@noble/ciphers": "^2.4.0",
|
|
258
|
+
"@react-spring/web": "^10.1.2",
|
|
259
|
+
"@use-gesture/react": "^10.3.1",
|
|
253
260
|
"dayjs": "^1.11.23",
|
|
254
261
|
"immer": "^11.1.21",
|
|
255
262
|
"lexical": "^0.52.0",
|
|
263
|
+
"react-refresh": "^0.19.0",
|
|
264
|
+
"scheduler": "^0.28.0",
|
|
256
265
|
"tailwind-merge": "^3.7.0",
|
|
266
|
+
"tailwind-scrollbar": "^4.0.2",
|
|
257
267
|
"tailwind-variants": "^3.3.1"
|
|
258
268
|
},
|
|
259
269
|
"peerDependencies": {
|
|
260
|
-
"@happy-dom/global-registrator": "^21.0.
|
|
270
|
+
"@happy-dom/global-registrator": "^21.0.4",
|
|
261
271
|
"@playwright/test": "^1.64.1",
|
|
262
|
-
"@react-spring/web": "^10.1.2",
|
|
263
|
-
"@use-gesture/react": "^10.3.1",
|
|
264
272
|
"bullmq": "^5.81.5",
|
|
265
273
|
"chance": "^1.1.13",
|
|
266
274
|
"firebase": "^13.0.0",
|
|
267
|
-
"happy-dom": "^21.0.
|
|
275
|
+
"happy-dom": "^21.0.4",
|
|
268
276
|
"ioredis": "^5.11.1",
|
|
269
277
|
"postgres": "^3.4.9",
|
|
270
278
|
"protobufjs": "^8.8.1",
|
|
271
279
|
"react": "19.3.1",
|
|
272
280
|
"react-dom": "19.3.1",
|
|
273
281
|
"react-icons": "^5.7.0",
|
|
274
|
-
"react-refresh": "^0.19.0",
|
|
275
282
|
"react-server-dom-webpack": "19.3.1",
|
|
276
|
-
"react-spring": "^10.0.4",
|
|
277
|
-
"scheduler": "^0.28.0",
|
|
278
|
-
"tailwind-scrollbar": "^4.0.2",
|
|
279
283
|
"typescript": "^6.0.3"
|
|
280
284
|
},
|
|
281
285
|
"peerDependenciesMeta": {
|
|
@@ -285,12 +289,6 @@
|
|
|
285
289
|
"@playwright/test": {
|
|
286
290
|
"optional": true
|
|
287
291
|
},
|
|
288
|
-
"@react-spring/web": {
|
|
289
|
-
"optional": true
|
|
290
|
-
},
|
|
291
|
-
"@use-gesture/react": {
|
|
292
|
-
"optional": true
|
|
293
|
-
},
|
|
294
292
|
"bullmq": {
|
|
295
293
|
"optional": true
|
|
296
294
|
},
|
|
@@ -321,21 +319,9 @@
|
|
|
321
319
|
"react-icons": {
|
|
322
320
|
"optional": true
|
|
323
321
|
},
|
|
324
|
-
"react-refresh": {
|
|
325
|
-
"optional": true
|
|
326
|
-
},
|
|
327
322
|
"react-server-dom-webpack": {
|
|
328
323
|
"optional": true
|
|
329
324
|
},
|
|
330
|
-
"react-spring": {
|
|
331
|
-
"optional": true
|
|
332
|
-
},
|
|
333
|
-
"scheduler": {
|
|
334
|
-
"optional": true
|
|
335
|
-
},
|
|
336
|
-
"tailwind-scrollbar": {
|
|
337
|
-
"optional": true
|
|
338
|
-
},
|
|
339
325
|
"typescript": {
|
|
340
326
|
"optional": true
|
|
341
327
|
}
|
package/server/akanApp.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
3
|
+
import "./lifecycle/loadBuildEnv";
|
|
2
4
|
import { mkdir, readdir, rm } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
4
|
-
import { Logger } from "akanjs/common";
|
|
6
|
+
import { Logger, TrustedProxy } from "akanjs/common";
|
|
5
7
|
import type { AkanChildRole, AkanChildStatus, AkanIpcMessage, AkanMetricsReport, AkanUpstream } from "akanjs/service";
|
|
6
8
|
import { getApiPrefix, getWsPrefix, normalizeRoutePrefix, resetEnvCache } from "../base/baseEnv";
|
|
7
9
|
import { CrossSiteGuard } from "../signal/CrossSiteGuard";
|
|
8
10
|
import { isTraceEnabled } from "../signal/trace";
|
|
9
11
|
import { AKAN_CHILD_HOST, makeAkanChildProxyHeaders } from "./akanAppHeaders";
|
|
10
12
|
import type { BuilderCsrReq, BuilderCsrRes, BuilderMessage, BuilderReq, BuilderRes } from "./artifact";
|
|
11
|
-
import { type AssetHeaderOptions, assetHeaders } from "./assetHeaders";
|
|
13
|
+
import { type AssetHeaderOptions, applySecurityHeaders, assetHeaders } from "./assetHeaders";
|
|
12
14
|
import { compressResponse, compressStreamResponse, encodedFileResponse } from "./contentEncoding";
|
|
13
15
|
import { isPortInUseError } from "./lifecycle/portInUse";
|
|
14
16
|
import { resolveRuntimeDir } from "./lifecycle/runtimeDir";
|
|
@@ -24,6 +26,7 @@ import type { OpsRoute } from "./ops/opsRoute";
|
|
|
24
26
|
import { StatusAccess } from "./ops/statusAccess";
|
|
25
27
|
import { ProcessMetricsCollector } from "./processMetricsCollector";
|
|
26
28
|
import { HostAllowlist } from "./routing/hostAllowlist";
|
|
29
|
+
import { ServerLimits } from "./routing/serverLimits";
|
|
27
30
|
import { WsUpgrade } from "./routing/wsUpgrade";
|
|
28
31
|
import { SelfExec } from "./selfExec";
|
|
29
32
|
import { resolveStaticPath } from "./staticPath";
|
|
@@ -59,6 +62,9 @@ interface SoloServer {
|
|
|
59
62
|
interface GatewayWsData {
|
|
60
63
|
childIdx: number;
|
|
61
64
|
upstream: WebSocket;
|
|
65
|
+
clientIp: string | null;
|
|
66
|
+
pending: (string | ArrayBuffer)[];
|
|
67
|
+
pendingBytes: number;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
type GatewayUpstream = {
|
|
@@ -147,6 +153,16 @@ export class AkanApp {
|
|
|
147
153
|
#logStream: LogStreamRoute | null = null;
|
|
148
154
|
#ops: OpsRoute | null = null;
|
|
149
155
|
#hostAllowlist = HostAllowlist.fromEnv();
|
|
156
|
+
readonly #limits = new ServerLimits();
|
|
157
|
+
|
|
158
|
+
static readonly #wsPendingLimitBytes = 1024 * 1024;
|
|
159
|
+
static readonly #wsUpstreamOpenTimeoutMs = 10_000;
|
|
160
|
+
readonly #wsCaps = {
|
|
161
|
+
total: AkanApp.#positiveIntEnv("AKAN_MAX_WEBSOCKETS"),
|
|
162
|
+
perIp: AkanApp.#positiveIntEnv("AKAN_MAX_WEBSOCKETS_PER_IP"),
|
|
163
|
+
};
|
|
164
|
+
#wsOpen = 0;
|
|
165
|
+
readonly #wsOpenByIp = new Map<string, number>();
|
|
150
166
|
readonly #statusAccess = StatusAccess.fromEnv();
|
|
151
167
|
static readonly #ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
152
168
|
#gatewayMetrics: AkanMetricsReport = {};
|
|
@@ -600,12 +616,14 @@ export class AkanApp {
|
|
|
600
616
|
|
|
601
617
|
#listen() {
|
|
602
618
|
this.#server = Bun.serve({
|
|
603
|
-
idleTimeout:
|
|
619
|
+
idleTimeout: this.#limits.idleTimeout,
|
|
620
|
+
maxRequestBodySize: this.#limits.uploadBodyBytes,
|
|
604
621
|
port: this.#port,
|
|
605
|
-
hostname:
|
|
606
|
-
fetch: (req, server) =>
|
|
622
|
+
hostname: ServerLimits.listenHostname(),
|
|
623
|
+
fetch: (req, server) =>
|
|
624
|
+
ServerLimits.holdOpen(server, req, this.#limits.idleTimeout, () => this.#handleFetch(req, server)),
|
|
607
625
|
websocket: {
|
|
608
|
-
idleTimeout:
|
|
626
|
+
idleTimeout: this.#limits.wsIdleTimeout,
|
|
609
627
|
maxPayloadLength: 16 * 1024 * 1024,
|
|
610
628
|
backpressureLimit: 1024 * 1024,
|
|
611
629
|
closeOnBackpressureLimit: true,
|
|
@@ -620,19 +638,28 @@ export class AkanApp {
|
|
|
620
638
|
|
|
621
639
|
async #handleFetch(req: Request, server: Bun.Server<GatewayWsData>): Promise<Response | undefined> {
|
|
622
640
|
if (this.#hostAllowlist && !this.#hostAllowlist.allows(req)) return this.#hostAllowlist.refuse();
|
|
641
|
+
const oversized = this.#limits.refuseDeclared(req);
|
|
642
|
+
if (oversized) return oversized;
|
|
623
643
|
const url = new URL(req.url);
|
|
624
|
-
if (url.pathname === "/_akan/app/health")
|
|
625
|
-
|
|
626
|
-
if (url.pathname ===
|
|
644
|
+
if (url.pathname === "/_akan/app/health")
|
|
645
|
+
return this.#statusAccess.health(req, () => this.#getHealthStatus(), AkanApp.#clientOf(req, server));
|
|
646
|
+
if (url.pathname === "/_akan/app/metrics")
|
|
647
|
+
return this.#statusAccess.metrics(req, () => this.#getMetricsStatus(), AkanApp.#clientOf(req, server));
|
|
648
|
+
if (url.pathname === LogStreamRoute.path && this.#logStream)
|
|
649
|
+
return this.#logStream.handle(req, AkanApp.#clientOf(req, server));
|
|
627
650
|
if (url.pathname === "/_akan/bench/ping") return new Response("ok");
|
|
628
651
|
if (url.pathname === AppInfo.publicPath) return AppInfo.handlePublic();
|
|
629
|
-
if (this.#ops?.matches(url.pathname)) return await this.#ops.handle(req);
|
|
652
|
+
if (this.#ops?.matches(url.pathname)) return await this.#ops.handle(req, AkanApp.#clientOf(req, server));
|
|
630
653
|
if (this.#isWebSocketPath(url.pathname)) return this.#upgradeWebSocket(req, server);
|
|
631
654
|
const assetResponse = await this.#serveImmutableArtifact(req, url);
|
|
632
655
|
if (assetResponse) return assetResponse;
|
|
633
656
|
return await this.#proxyHttp(req, server);
|
|
634
657
|
}
|
|
635
658
|
|
|
659
|
+
static #clientOf(req: Request, server: Bun.Server<GatewayWsData>) {
|
|
660
|
+
return TrustedProxy.clientAddress(req.headers, server.requestIP(req)?.address);
|
|
661
|
+
}
|
|
662
|
+
|
|
636
663
|
#isWebSocketPath(pathname: string) {
|
|
637
664
|
return pathname === `${this.#prefix}${this.#websocketPrefix}` || pathname === "/_akan/hmr";
|
|
638
665
|
}
|
|
@@ -672,40 +699,68 @@ export class AkanApp {
|
|
|
672
699
|
return new Response("Forbidden", { status: 403 });
|
|
673
700
|
}
|
|
674
701
|
if (!WsUpgrade.isRequested(req)) return WsUpgrade.required();
|
|
702
|
+
const peer = server.requestIP(req) ?? undefined;
|
|
703
|
+
const clientIp = TrustedProxy.clientAddress(req.headers, peer?.address);
|
|
704
|
+
const refused = this.#refuseWebSocket(clientIp);
|
|
705
|
+
if (refused) return refused;
|
|
675
706
|
const upstreamWs = new WebSocket(`ws://${upstream.host}:${upstream.port}${url.pathname}${url.search}`, {
|
|
676
|
-
headers: makeAkanChildProxyHeaders(req, child.idx,
|
|
707
|
+
headers: makeAkanChildProxyHeaders(req, child.idx, peer),
|
|
677
708
|
} as unknown as string[]);
|
|
678
709
|
|
|
679
|
-
const upgraded = server.upgrade(req, {
|
|
710
|
+
const upgraded = server.upgrade(req, {
|
|
711
|
+
data: { childIdx: child.idx, upstream: upstreamWs, clientIp, pending: [], pendingBytes: 0 },
|
|
712
|
+
});
|
|
680
713
|
if (!upgraded) {
|
|
681
714
|
upstreamWs.close();
|
|
682
715
|
return new Response("WebSocket upgrade failed", { status: 500 });
|
|
683
716
|
}
|
|
684
717
|
child.metrics.activeWebSockets = (child.metrics.activeWebSockets ?? 0) + 1;
|
|
718
|
+
this.#wsOpen += 1;
|
|
719
|
+
if (clientIp) this.#wsOpenByIp.set(clientIp, (this.#wsOpenByIp.get(clientIp) ?? 0) + 1);
|
|
685
720
|
return undefined;
|
|
686
721
|
}
|
|
687
722
|
|
|
723
|
+
#refuseWebSocket(clientIp: string | null): Response | null {
|
|
724
|
+
const headers = { "cache-control": "no-store" };
|
|
725
|
+
if (this.#wsCaps.total && this.#wsOpen >= this.#wsCaps.total)
|
|
726
|
+
return new Response("Too many open websockets", { status: 503, headers });
|
|
727
|
+
if (this.#wsCaps.perIp && clientIp && (this.#wsOpenByIp.get(clientIp) ?? 0) >= this.#wsCaps.perIp)
|
|
728
|
+
return new Response("Too many open websockets from this address", { status: 429, headers });
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
static #positiveIntEnv(name: string): number | null {
|
|
733
|
+
const value = Number(process.env[name]);
|
|
734
|
+
return Number.isInteger(value) && value > 0 ? value : null;
|
|
735
|
+
}
|
|
736
|
+
|
|
688
737
|
#handleWsOpen(ws: Bun.ServerWebSocket<GatewayWsData>) {
|
|
689
738
|
const upstream = ws.data.upstream;
|
|
690
|
-
const
|
|
739
|
+
const openTimer =
|
|
740
|
+
upstream.readyState === WebSocket.CONNECTING
|
|
741
|
+
? setTimeout(() => {
|
|
742
|
+
if (upstream.readyState === WebSocket.OPEN) return;
|
|
743
|
+
upstream.close();
|
|
744
|
+
ws.close(1013, "upstream websocket did not open");
|
|
745
|
+
}, AkanApp.#wsUpstreamOpenTimeoutMs)
|
|
746
|
+
: null;
|
|
691
747
|
upstream.addEventListener("open", () => {
|
|
692
|
-
|
|
748
|
+
if (openTimer) clearTimeout(openTimer);
|
|
749
|
+
for (const message of ws.data.pending.splice(0)) upstream.send(message);
|
|
750
|
+
ws.data.pendingBytes = 0;
|
|
693
751
|
});
|
|
694
752
|
upstream.addEventListener("message", (event) => {
|
|
695
753
|
const result = ws.send(event.data as string | ArrayBuffer);
|
|
696
754
|
if (result === 0) upstream.close();
|
|
697
755
|
});
|
|
698
756
|
upstream.addEventListener("close", (event) => {
|
|
757
|
+
if (openTimer) clearTimeout(openTimer);
|
|
699
758
|
ws.close(relayableCloseCode(event.code), event.reason);
|
|
700
759
|
});
|
|
701
760
|
upstream.addEventListener("error", () => ws.close(1011, "upstream websocket error"));
|
|
702
|
-
Object.assign(ws.data, { pending });
|
|
703
761
|
}
|
|
704
762
|
|
|
705
|
-
#handleWsMessage(
|
|
706
|
-
ws: Bun.ServerWebSocket<GatewayWsData & { pending?: (string | ArrayBuffer)[] }>,
|
|
707
|
-
message: string | ArrayBuffer | Uint8Array,
|
|
708
|
-
) {
|
|
763
|
+
#handleWsMessage(ws: Bun.ServerWebSocket<GatewayWsData>, message: string | ArrayBuffer | Uint8Array) {
|
|
709
764
|
const upstream = ws.data.upstream;
|
|
710
765
|
const payload =
|
|
711
766
|
typeof message === "string"
|
|
@@ -713,14 +768,29 @@ export class AkanApp {
|
|
|
713
768
|
: message instanceof ArrayBuffer
|
|
714
769
|
? message
|
|
715
770
|
: new Uint8Array(message).slice().buffer;
|
|
716
|
-
if (upstream.readyState === WebSocket.OPEN)
|
|
717
|
-
|
|
771
|
+
if (upstream.readyState === WebSocket.OPEN) {
|
|
772
|
+
upstream.send(payload);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const size = typeof payload === "string" ? Buffer.byteLength(payload) : payload.byteLength;
|
|
776
|
+
if (ws.data.pendingBytes + size > AkanApp.#wsPendingLimitBytes) {
|
|
777
|
+
ws.data.pending.length = 0;
|
|
778
|
+
ws.close(1009, "too much data before the upstream opened");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
ws.data.pending.push(payload as string | ArrayBuffer);
|
|
782
|
+
ws.data.pendingBytes += size;
|
|
718
783
|
}
|
|
719
784
|
|
|
720
785
|
#handleWsClose(ws: Bun.ServerWebSocket<GatewayWsData>, code: number, reason: string) {
|
|
721
786
|
ws.data.upstream.close(relayableCloseCode(code), reason);
|
|
722
787
|
const child = this.#children.get(ws.data.childIdx);
|
|
723
788
|
if (child) child.metrics.activeWebSockets = Math.max(0, (child.metrics.activeWebSockets ?? 1) - 1);
|
|
789
|
+
this.#wsOpen = Math.max(0, this.#wsOpen - 1);
|
|
790
|
+
const { clientIp } = ws.data;
|
|
791
|
+
const remaining = clientIp ? (this.#wsOpenByIp.get(clientIp) ?? 1) - 1 : 0;
|
|
792
|
+
if (clientIp && remaining > 0) this.#wsOpenByIp.set(clientIp, remaining);
|
|
793
|
+
else if (clientIp) this.#wsOpenByIp.delete(clientIp);
|
|
724
794
|
}
|
|
725
795
|
|
|
726
796
|
#getHealthStatus() {
|
|
@@ -779,23 +849,30 @@ export class AkanApp {
|
|
|
779
849
|
const { socketPath } = child.upstream;
|
|
780
850
|
const url = new URL(req.url);
|
|
781
851
|
const upstreamUrl = `http://akan-child${url.pathname}${url.search}`;
|
|
782
|
-
const headers = makeAkanChildProxyHeaders(req, child.idx, server.requestIP(req));
|
|
852
|
+
const headers = makeAkanChildProxyHeaders(req, child.idx, server.requestIP(req) ?? undefined);
|
|
783
853
|
child.metrics.activeRequests = (child.metrics.activeRequests ?? 0) + 1;
|
|
784
854
|
child.metrics.totalRequests = (child.metrics.totalRequests ?? 0) + 1;
|
|
785
855
|
const traced = isTraceEnabled();
|
|
786
856
|
const hopStart = traced ? performance.now() : 0;
|
|
857
|
+
const chunkedLimit = req.headers.has("content-length") ? null : this.#limits.bodyLimitOf(req);
|
|
787
858
|
try {
|
|
788
859
|
const upstreamRes = await fetch(upstreamUrl, {
|
|
789
860
|
unix: socketPath,
|
|
790
861
|
method: req.method,
|
|
791
862
|
headers,
|
|
792
|
-
body:
|
|
863
|
+
body:
|
|
864
|
+
req.method === "GET" || req.method === "HEAD"
|
|
865
|
+
? undefined
|
|
866
|
+
: chunkedLimit !== null && req.body
|
|
867
|
+
? ServerLimits.limitBody(req.body, chunkedLimit)
|
|
868
|
+
: req.body,
|
|
793
869
|
signal: req.signal,
|
|
794
870
|
redirect: "manual",
|
|
795
871
|
decompress: false,
|
|
796
872
|
});
|
|
797
873
|
return await this.#proxyResponse(req, upstreamRes);
|
|
798
874
|
} catch (error) {
|
|
875
|
+
if (ServerLimits.isPayloadTooLarge(error)) return ServerLimits.payloadTooLarge();
|
|
799
876
|
if (AkanApp.#isUpstreamOpenFailure(error)) {
|
|
800
877
|
this.logger.error(`Child ${child.idx}/${child.role} upstream is unreachable (${socketPath}); restarting`);
|
|
801
878
|
this.#scheduleChildRestart(child, child.proc, "upstream-open-failed");
|
|
@@ -844,13 +921,10 @@ export class AkanApp {
|
|
|
844
921
|
detail,
|
|
845
922
|
note: "The replica is restarting — this page reloads itself as soon as it answers.",
|
|
846
923
|
}),
|
|
847
|
-
{ status: 502, headers:
|
|
924
|
+
{ status: 502, headers: AkanApp.#statusHeaders("text/html; charset=utf-8") },
|
|
848
925
|
);
|
|
849
926
|
}
|
|
850
|
-
return new Response(detail, {
|
|
851
|
-
status: 502,
|
|
852
|
-
headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
|
|
853
|
-
});
|
|
927
|
+
return new Response(detail, { status: 502, headers: AkanApp.#statusHeaders("text/plain; charset=utf-8") });
|
|
854
928
|
}
|
|
855
929
|
|
|
856
930
|
async #pickReadyFederationChild(req: Request): Promise<ChildState | null> {
|
|
@@ -895,7 +969,7 @@ export class AkanApp {
|
|
|
895
969
|
if (req.headers.get("accept")?.includes("text/html")) {
|
|
896
970
|
return new Response(AkanApp.#statusPageHtml(page), {
|
|
897
971
|
status: 503,
|
|
898
|
-
headers:
|
|
972
|
+
headers: AkanApp.#statusHeaders("text/html; charset=utf-8"),
|
|
899
973
|
});
|
|
900
974
|
}
|
|
901
975
|
return AkanApp.#unavailableResponse(req, page.text);
|
|
@@ -910,6 +984,11 @@ export class AkanApp {
|
|
|
910
984
|
);
|
|
911
985
|
}
|
|
912
986
|
|
|
987
|
+
static #statusHeaders(contentType: string): Headers {
|
|
988
|
+
const headers = new Headers({ "content-type": contentType, "cache-control": "no-store" });
|
|
989
|
+
return applySecurityHeaders(headers, { html: contentType.startsWith("text/html") });
|
|
990
|
+
}
|
|
991
|
+
|
|
913
992
|
static #statusPageHtml({ heading, detail, note }: { heading: string; detail: string; note: string }) {
|
|
914
993
|
return `<!doctype html>
|
|
915
994
|
<html>
|
package/server/akanServer.ts
CHANGED
|
@@ -49,6 +49,7 @@ import type { AppWsData } from "./routing/appWsData";
|
|
|
49
49
|
import { HostAllowlist } from "./routing/hostAllowlist";
|
|
50
50
|
import { RoomReplay } from "./routing/roomReplay";
|
|
51
51
|
import { RouteHosts } from "./routing/routeHosts";
|
|
52
|
+
import { ServerLimits } from "./routing/serverLimits";
|
|
52
53
|
import { createSoloAppRoutes } from "./routing/soloAppRoutes";
|
|
53
54
|
import {
|
|
54
55
|
getWebConfigFromEnv,
|
|
@@ -443,6 +444,7 @@ export class AkanServer {
|
|
|
443
444
|
this.logger.verbose(`${this.name} is serving on ${unix ? `unix://${unix}` : `port ${port}`}`);
|
|
444
445
|
const { routes, routeOptions, wsRoutes, builtinRoutes, renderEnvRoutes, hmrHub, webRouter, webProxyRunner } =
|
|
445
446
|
this.#prepared;
|
|
447
|
+
const limits = new ServerLimits();
|
|
446
448
|
const websocketHandlers = {
|
|
447
449
|
...ApiRouter.buildWebsocketHandlers({
|
|
448
450
|
wsRoutes,
|
|
@@ -459,11 +461,14 @@ export class AkanServer {
|
|
|
459
461
|
logger: this.logger,
|
|
460
462
|
onDrain: () => this.#binaryPubsub.flush(),
|
|
461
463
|
}),
|
|
464
|
+
idleTimeout: limits.wsIdleTimeout,
|
|
462
465
|
data: {},
|
|
463
466
|
} as Bun.WebSocketHandler<AppWsData | HmrWsData>;
|
|
464
467
|
|
|
465
468
|
const hostAllowlist = unix ? null : HostAllowlist.fromEnv();
|
|
466
|
-
const hostname =
|
|
469
|
+
const hostname = ServerLimits.listenHostname();
|
|
470
|
+
|
|
471
|
+
const idleTimeout = unix ? 0 : limits.idleTimeout;
|
|
467
472
|
const buildRoutes = (upgradeAppWs: ApiRouteInputs["upgradeAppWs"], allowlist: HostAllowlist | null) =>
|
|
468
473
|
ApiRouter.buildRoutes({
|
|
469
474
|
prefix: this.prefix,
|
|
@@ -475,13 +480,19 @@ export class AkanServer {
|
|
|
475
480
|
upgradeAppWs,
|
|
476
481
|
webProxyRunner,
|
|
477
482
|
hostAllowlist: allowlist,
|
|
483
|
+
bodyLimits: limits,
|
|
484
|
+
countChunkedBodies: !unix,
|
|
485
|
+
idleTimeout,
|
|
478
486
|
});
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
487
|
+
const serve = () =>
|
|
488
|
+
Bun.serve({
|
|
489
|
+
idleTimeout,
|
|
490
|
+
maxRequestBodySize: limits.uploadBodyBytes,
|
|
491
|
+
...(unix ? { unix } : { port, hostname }),
|
|
492
|
+
routes: buildRoutes((req, data) => this.#server?.upgrade(req, { data }) ?? false, hostAllowlist),
|
|
493
|
+
websocket: websocketHandlers,
|
|
494
|
+
} as Parameters<typeof Bun.serve>[0]);
|
|
495
|
+
this.#server = unix ? ServerLimits.serveOwnerOnly(unix, serve) : serve();
|
|
485
496
|
if (unix && process.env.AKAN_CHILD_WS_PORT) {
|
|
486
497
|
const preferredWsPort = Number(process.env.AKAN_CHILD_WS_PORT);
|
|
487
498
|
|
package/server/assetHeaders.ts
CHANGED
|
@@ -7,10 +7,29 @@ export interface AssetHeaderOptions {
|
|
|
7
7
|
export const applySecurityHeaders = (headers: Headers, { html = false } = {}): Headers => {
|
|
8
8
|
headers.set("X-Content-Type-Options", "nosniff");
|
|
9
9
|
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
10
|
-
if (html) headers
|
|
10
|
+
if (!html) return headers;
|
|
11
|
+
headers.set("X-Frame-Options", "SAMEORIGIN");
|
|
12
|
+
const policy = htmlContentSecurityPolicy();
|
|
13
|
+
if (policy) headers.set("Content-Security-Policy", policy);
|
|
11
14
|
return headers;
|
|
12
15
|
};
|
|
13
16
|
|
|
17
|
+
export const isOff = (value: string) => ["off", "false", "0", "none"].includes(value.toLowerCase());
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_HTML_CONTENT_SECURITY_POLICY = "frame-ancestors 'self'; base-uri 'self'";
|
|
20
|
+
|
|
21
|
+
export const htmlContentSecurityPolicy = (raw = process.env.AKAN_CONTENT_SECURITY_POLICY?.trim()): string | null => {
|
|
22
|
+
if (!raw || /[\r\n]/.test(raw)) return DEFAULT_HTML_CONTENT_SECURITY_POLICY;
|
|
23
|
+
return isOff(raw) ? null : raw;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_STRICT_TRANSPORT_SECURITY = "max-age=31536000";
|
|
27
|
+
|
|
28
|
+
export const strictTransportSecurity = (raw = process.env.AKAN_HSTS?.trim()): string | null => {
|
|
29
|
+
if (!raw || /[\r\n]/.test(raw)) return DEFAULT_STRICT_TRANSPORT_SECURITY;
|
|
30
|
+
return isOff(raw) ? null : raw;
|
|
31
|
+
};
|
|
32
|
+
|
|
14
33
|
export const assetHeaders = ({ contentType, cacheControl, anyOrigin }: AssetHeaderOptions): Headers => {
|
|
15
34
|
const headers = new Headers({ "Content-Type": contentType });
|
|
16
35
|
if (cacheControl) headers.set("Cache-Control", cacheControl);
|
package/server/cachePolicy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CookieJar, originFromRequest } from "akanjs/common";
|
|
2
2
|
import type { AkanDynamicUsage, AkanRequestPolicy } from "akanjs/fetch";
|
|
3
|
+
import { isOff } from "./assetHeaders";
|
|
3
4
|
|
|
4
5
|
export const DEFAULT_ROUTE_CACHE_TTL_SECONDS = 30;
|
|
5
6
|
|
|
@@ -88,14 +89,26 @@ export function getClientFacingOrigin(request: Request, url = new URL(request.ur
|
|
|
88
89
|
return originFromRequest(request.headers, url);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export function
|
|
92
|
-
if (request.
|
|
93
|
-
if (request.headers.has("authorization")) return false;
|
|
92
|
+
export function carriesCredentials(request: Request): boolean {
|
|
93
|
+
if (request.headers.has("authorization")) return true;
|
|
94
94
|
const cookie = request.headers.get("cookie");
|
|
95
|
-
if (!cookie) return
|
|
95
|
+
if (!cookie) return false;
|
|
96
96
|
return CookieJar.parse(cookie)
|
|
97
97
|
.names()
|
|
98
|
-
.
|
|
98
|
+
.some((name) => name !== "theme");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isPublicRouteCacheableRequest(request: Request): boolean {
|
|
102
|
+
return request.method === "GET" && !carriesCredentials(request);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveHtmlCacheControl(
|
|
106
|
+
request: Request,
|
|
107
|
+
raw = process.env.AKAN_HTML_CACHE_CONTROL?.trim(),
|
|
108
|
+
): string | null {
|
|
109
|
+
if (raw && isOff(raw)) return null;
|
|
110
|
+
if (carriesCredentials(request)) return "private, no-cache";
|
|
111
|
+
return raw && !/[\r\n]/.test(raw) ? raw : "no-cache";
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
const isAtOrUnder = (value: string, prefix: string) =>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BaseEnv, INJECT_META } from "akanjs/base";
|
|
2
|
+
import { modelNames } from "akanjs/common";
|
|
2
3
|
import type { AdaptorCls, InjectInfo, ServiceCls } from "akanjs/service";
|
|
3
4
|
import type { DiLifecycle } from "../di/diLifecycle";
|
|
4
5
|
import type { DepEdge, DepEdgeKind, DepNode, DepNodeKind, DepsData } from "./types";
|
|
@@ -126,7 +127,7 @@ export class DepsSerializer {
|
|
|
126
127
|
const kind = injectInfo.type as DepEdgeKind;
|
|
127
128
|
switch (kind) {
|
|
128
129
|
case "database":
|
|
129
|
-
return { from, to: this.#node("adaptor",
|
|
130
|
+
return { from, to: this.#node("adaptor", modelNames(injectInfo.parentRefName).modelModel), kind, prop };
|
|
130
131
|
case "service":
|
|
131
132
|
return prop.endsWith("Service")
|
|
132
133
|
? { from, to: this.#node("service", prop.slice(0, -"Service".length)), kind, prop }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BaseEnv } from "akanjs/base";
|
|
2
2
|
import { Logger } from "akanjs/common";
|
|
3
3
|
import { DictionaryRegistry, type RootDictionary } from "akanjs/dictionary";
|
|
4
|
+
import { isLocalDevRuntime } from "../../signal/trace";
|
|
4
5
|
import type { DiLifecycle } from "../di/diLifecycle";
|
|
5
6
|
import type { HttpRoutes } from "../types";
|
|
6
7
|
import { ConstantSerializer } from "./constantSerializer";
|
|
@@ -24,12 +25,12 @@ export class DevtoolsRouter {
|
|
|
24
25
|
static readonly basePath = "/_akan";
|
|
25
26
|
static readonly logger = new Logger("DevtoolsRouter");
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
+
/** A local dev runtime only, unless `AKAN_DEVTOOLS` ("true"/"1", "false"/"0") overrides it, e.g. to debug a deployed env. */
|
|
28
29
|
static isEnabled(env: BaseEnv): boolean {
|
|
29
30
|
const override = process.env.AKAN_DEVTOOLS;
|
|
30
31
|
if (override === "false" || override === "0") return false;
|
|
31
32
|
if (override === "true" || override === "1") return true;
|
|
32
|
-
return env
|
|
33
|
+
return isLocalDevRuntime(env);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
readonly #context: DevtoolsRouterContext;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type PrimitiveScalar,
|
|
7
7
|
SLICE_META,
|
|
8
8
|
} from "akanjs/base";
|
|
9
|
-
import {
|
|
9
|
+
import { sliceNames } from "akanjs/common";
|
|
10
10
|
import { ConstantRegistry, type ConstantType } from "akanjs/constant";
|
|
11
11
|
import type {
|
|
12
12
|
ArgInfo,
|
|
@@ -148,10 +148,10 @@ export class SignalSerializer {
|
|
|
148
148
|
static #sliceDerivedKeys(refName: string, sliceCls: SliceCls | undefined): Set<string> {
|
|
149
149
|
const sliceMeta = (sliceCls?.[SLICE_META] ?? {}) as Record<string, SliceInfo>;
|
|
150
150
|
return new Set(
|
|
151
|
-
Object.keys(sliceMeta).flatMap((key) =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
Object.keys(sliceMeta).flatMap((key) => {
|
|
152
|
+
const { list, insight } = sliceNames(refName, key).endpoint;
|
|
153
|
+
return [list, insight];
|
|
154
|
+
}),
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -15,7 +15,8 @@ export class ClientReport {
|
|
|
15
15
|
static readonly #fieldLimit = 4_000;
|
|
16
16
|
static readonly #stackLines = 6;
|
|
17
17
|
|
|
18
|
-
static readonly #controlChars = /[\u0000-\u0008\u000b-\u001f\u007f]/g;
|
|
18
|
+
static readonly #controlChars = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
19
|
+
static readonly #lineBreaks = /[\n\u2028\u2029]/g;
|
|
19
20
|
|
|
20
21
|
static parse(value: unknown): HmrClientReport | null {
|
|
21
22
|
if (!value || typeof value !== "object") return null;
|
|
@@ -27,12 +28,12 @@ export class ClientReport {
|
|
|
27
28
|
return {
|
|
28
29
|
type: "client-report",
|
|
29
30
|
kind: report.kind,
|
|
30
|
-
message: ClientReport.#
|
|
31
|
-
href: ClientReport.#
|
|
31
|
+
message: ClientReport.#line(report.message),
|
|
32
|
+
href: ClientReport.#line(report.href),
|
|
32
33
|
client: report.client,
|
|
33
34
|
...(typeof report.stack === "string" && report.stack ? { stack: ClientReport.#clean(report.stack) } : {}),
|
|
34
|
-
...(typeof report.platform === "string" ? { platform: ClientReport.#
|
|
35
|
-
...(typeof report.target === "string" ? { target: ClientReport.#
|
|
35
|
+
...(typeof report.platform === "string" ? { platform: ClientReport.#line(report.platform) } : {}),
|
|
36
|
+
...(typeof report.target === "string" ? { target: ClientReport.#line(report.target) } : {}),
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -67,4 +68,8 @@ export class ClientReport {
|
|
|
67
68
|
static #clean(text: string) {
|
|
68
69
|
return Logger.stripAnsi(text).replace(ClientReport.#controlChars, "").slice(0, ClientReport.#fieldLimit);
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
static #line(text: string) {
|
|
73
|
+
return ClientReport.#clean(text).replace(ClientReport.#lineBreaks, "\\n");
|
|
74
|
+
}
|
|
70
75
|
}
|