akanjs 3.0.2 → 3.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/base/baseEnv.ts +0 -4
- package/base/primitiveRegistry.ts +2 -2
- package/client/cookie.ts +5 -1
- package/client/csrLocation.ts +122 -0
- package/client/csrTypes.ts +36 -4
- package/client/device.ts +2 -1
- package/client/frameConfig.ts +78 -7
- package/client/index.ts +3 -0
- package/client/makePageProto.tsx +3 -2
- package/client/native/external-view.ts +1 -0
- package/client/route/RootLayoutDefinition.ts +7 -1
- package/client/route/RouteHead.ts +108 -0
- package/client/route/index.ts +1 -0
- package/client/router.ts +36 -23
- package/client/rscNavigation.ts +8 -2
- package/client/viewportMeta.ts +60 -0
- package/client/webManifest.ts +18 -0
- package/common/Logger.ts +7 -2
- package/common/PipelineNames.ts +31 -0
- package/common/PipelinePattern.ts +70 -0
- package/common/PipelinePlanSchema.ts +490 -0
- package/common/backoff.ts +14 -0
- package/common/index.ts +39 -0
- package/common/pipelinePlan.ts +259 -0
- package/common/routeConvention.ts +1 -0
- package/common/types.ts +17 -0
- package/constant/constantRegistry.ts +26 -6
- package/dictionary/base.dictionary.ts +12 -0
- package/dictionary/trans.ts +9 -1
- package/document/into.ts +5 -0
- package/fetch/client/fetchClient.ts +135 -31
- package/fetch/client/httpClient.ts +45 -0
- package/fetch/client/wsClient.ts +125 -16
- package/fetch/fetchType/buildFetch.type.ts +1 -0
- package/index.ts +243 -10
- package/package.json +1 -5
- package/server/akanApp.ts +5 -2
- package/server/akanServer.ts +44 -8
- package/server/artifact/builderRpc.ts +8 -3
- package/server/artifact/ipcTypes.ts +5 -2
- package/server/di/diLifecycle.ts +39 -3
- package/server/di/utils.ts +28 -1
- package/server/ed25519Key.ts +48 -0
- package/server/index.ts +1 -0
- package/server/machine/index.ts +2 -0
- package/server/machine/machineAccount.ts +27 -0
- package/server/machine/machineToken.ts +151 -0
- package/server/mcp/McpApps.ts +256 -0
- package/server/mcp/McpDispatcher.ts +31 -7
- package/server/mcp/McpRouter.ts +209 -46
- package/server/ops/opsToken.ts +3 -10
- package/server/resolver/database.resolver.ts +3 -0
- package/server/resolver/signal.resolver.ts +40 -3
- package/server/routeElementComposer.tsx +78 -2
- package/server/routeState.ts +6 -0
- package/server/routeTreeBuilder.ts +26 -9
- package/server/routing/apiRouter.ts +6 -3
- package/server/routing/roomReplay.ts +88 -0
- package/server/rscClient.tsx +53 -8
- package/server/rscWorker.tsx +141 -27
- package/server/rscWorkerHost.ts +18 -5
- package/server/sitemap.ts +14 -0
- package/server/ssrFromRscRenderer.tsx +12 -6
- package/server/ssrTypes.ts +3 -1
- package/server/systemPageDocument.tsx +38 -30
- package/server/types.tsx +2 -0
- package/server/webRouter.ts +74 -5
- package/service/adapt.ts +1 -1
- package/service/injectInfo.ts +9 -1
- package/service/ipcTypes.ts +2 -1
- package/service/predefinedAdaptor/database.adaptor.ts +1 -146
- package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
- package/service/predefinedAdaptor/queueBackoff.ts +12 -0
- package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
- package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
- package/service/predefinedAdaptor/sql/types.ts +8 -6
- package/service/predefinedAdaptor/sql/values.ts +0 -23
- package/service/serve.ts +5 -1
- package/signal/agent.signal.ts +1 -1
- package/signal/base.signal.ts +2 -2
- package/signal/endpointIdempotency.ts +53 -0
- package/signal/internalArg.ts +14 -0
- package/signal/mcp/McpDocument.ts +38 -1
- package/signal/mcp/mcpProtocol.ts +18 -0
- package/signal/signalContext.ts +58 -30
- package/signal/signalRegistry.ts +14 -4
- package/signal/types.ts +18 -3
- package/store/action.ts +17 -19
- package/store/agent/StoreSurfaceSource.ts +1 -1
- package/store/agentic/StToolBuilder.ts +3 -0
- package/test/conformance.ts +9 -33
- package/types/base/baseEnv.d.ts +0 -4
- package/types/base/primitiveRegistry.d.ts +1 -1
- package/types/client/csrLocation.d.ts +32 -0
- package/types/client/csrTypes.d.ts +43 -4
- package/types/client/frameConfig.d.ts +14 -2
- package/types/client/index.d.ts +3 -0
- package/types/client/native/external-view.d.ts +1 -0
- package/types/client/route/RootLayoutDefinition.d.ts +2 -1
- package/types/client/route/RouteHead.d.ts +13 -0
- package/types/client/route/index.d.ts +1 -0
- package/types/client/router.d.ts +6 -0
- package/types/client/rscNavigation.d.ts +4 -0
- package/types/client/viewportMeta.d.ts +18 -0
- package/types/client/webManifest.d.ts +4 -0
- package/types/common/PipelineNames.d.ts +6 -0
- package/types/common/PipelinePattern.d.ts +14 -0
- package/types/common/PipelinePlanSchema.d.ts +9 -0
- package/types/common/backoff.d.ts +10 -0
- package/types/common/index.d.ts +5 -0
- package/types/common/pipelinePlan.d.ts +224 -0
- package/types/common/types.d.ts +16 -0
- package/types/constant/constantRegistry.d.ts +5 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/into.d.ts +9 -0
- package/types/fetch/client/fetchClient.d.ts +31 -4
- package/types/fetch/client/httpClient.d.ts +8 -0
- package/types/fetch/client/wsClient.d.ts +26 -2
- package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
- package/types/index.d.ts +230 -9
- package/types/server/akanServer.d.ts +16 -1
- package/types/server/artifact/builderRpc.d.ts +7 -2
- package/types/server/artifact/ipcTypes.d.ts +5 -1
- package/types/server/di/utils.d.ts +1 -0
- package/types/server/ed25519Key.d.ts +13 -0
- package/types/server/index.d.ts +1 -0
- package/types/server/machine/index.d.ts +2 -0
- package/types/server/machine/machineAccount.d.ts +5 -0
- package/types/server/machine/machineToken.d.ts +29 -0
- package/types/server/machine.d.ts +1 -0
- package/types/server/mcp/McpApps.d.ts +50 -0
- package/types/server/mcp/McpDispatcher.d.ts +4 -1
- package/types/server/mcp/McpRouter.d.ts +4 -0
- package/types/server/resolver/signal.resolver.d.ts +2 -1
- package/types/server/routeElementComposer.d.ts +24 -4
- package/types/server/routeState.d.ts +4 -1
- package/types/server/routing/roomReplay.d.ts +24 -0
- package/types/server/rscWorkerHost.d.ts +5 -2
- package/types/server/sitemap.d.ts +2 -0
- package/types/server/ssrFromRscRenderer.d.ts +3 -3
- package/types/server/ssrTypes.d.ts +4 -1
- package/types/server/systemPageDocument.d.ts +2 -0
- package/types/server/types.d.ts +4 -1
- package/types/server/webRouter.d.ts +3 -1
- package/types/service/adapt.d.ts +1 -1
- package/types/service/ipcTypes.d.ts +2 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
- package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
- package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
- package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
- package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
- package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
- package/types/service/serve.d.ts +3 -0
- package/types/signal/endpointIdempotency.d.ts +7 -0
- package/types/signal/internalArg.d.ts +10 -0
- package/types/signal/mcp/McpDocument.d.ts +8 -0
- package/types/signal/mcp/mcpProtocol.d.ts +17 -0
- package/types/signal/signalContext.d.ts +5 -3
- package/types/signal/signalRegistry.d.ts +4 -4
- package/types/signal/types.d.ts +21 -1
- package/types/store/agentic/StToolBuilder.d.ts +2 -0
- package/types/test/conformance.d.ts +7 -10
- package/types/ui/Agent/Chat.d.ts +5 -1
- package/types/ui/Agent/EmbedApproval.d.ts +1 -0
- package/types/ui/Agent/index.d.ts +3 -0
- package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +18 -0
- package/types/ui/BottomSheet.d.ts +1 -0
- package/types/ui/Constant/Doc.d.ts +11 -17
- package/types/ui/Constant/index.d.ts +7 -2
- package/types/ui/Constant/schemaDoc.d.ts +11 -0
- package/types/ui/Dialog/Modal.d.ts +6 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Dialog/modalSurface.d.ts +12 -0
- package/types/ui/Layout/BottomInset.d.ts +1 -1
- package/types/ui/Layout/BottomTab.d.ts +11 -2
- package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
- package/types/ui/Layout/Navbar.d.ts +4 -1
- package/types/ui/Layout/TopInset.d.ts +1 -1
- package/types/ui/Layout/index.d.ts +4 -4
- package/types/ui/Link/Action.d.ts +5 -1
- package/types/ui/Link/Anchor.d.ts +5 -3
- package/types/ui/Link/activePath.d.ts +13 -0
- package/types/ui/Link/index.d.ts +1 -1
- package/types/ui/Modal.d.ts +6 -1
- package/types/ui/Model/EditModal.d.ts +6 -1
- package/types/ui/Model/New.d.ts +6 -1
- package/types/ui/Model/ViewEditModal.d.ts +6 -1
- package/types/ui/Model/ViewModal.d.ts +6 -1
- package/types/ui/Model/newFormOrigin.d.ts +7 -0
- package/types/ui/Portal.d.ts +3 -1
- package/types/ui/Reference/origin.d.ts +12 -0
- package/types/ui/Signal/Doc.d.ts +23 -18
- package/types/ui/Signal/Listener.d.ts +2 -6
- package/types/ui/Signal/Object.d.ts +6 -10
- package/types/ui/Signal/Response.d.ts +3 -6
- package/types/ui/Signal/RestApi.d.ts +11 -15
- package/types/ui/Signal/WebSocket.d.ts +11 -21
- package/types/ui/Signal/endpointEntries.d.ts +8 -0
- package/types/ui/Signal/index.d.ts +35 -12
- package/types/ui/Signal/signalText.d.ts +178 -0
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +4 -3
- package/types/ui/System/Common.d.ts +27 -6
- package/types/ui/System/SSR.d.ts +6 -6
- package/types/ui/Tooltip.d.ts +4 -2
- package/types/ui/index.d.ts +2 -1
- package/types/ui/overlayLayer.d.ts +6 -0
- package/types/ui/overlayPosition.d.ts +17 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
- package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
- package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
- package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +12 -4
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/dispatcher.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
- package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
- package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
- package/types/vendor/use-agentic/AgentActivity.d.ts +3 -1
- package/types/vendor/use-agentic/AgenticSurface.d.ts +2 -2
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/webkit/CsrStack.d.ts +1 -1
- package/types/webkit/EmbeddedLayout.d.ts +10 -0
- package/types/webkit/McpAppAgent.d.ts +14 -0
- package/types/webkit/McpAppApprovals.d.ts +8 -0
- package/types/webkit/McpAppBridge.d.ts +101 -0
- package/types/webkit/McpAppView.d.ts +21 -0
- package/types/webkit/RenderLayer.d.ts +3 -1
- package/types/webkit/index.d.ts +1 -0
- package/types/webkit/useLocation.d.ts +1 -1
- package/types/webkit/useVisualViewportFrame.d.ts +13 -0
- package/ui/Agent/Chat.tsx +29 -10
- package/ui/Agent/Composer.tsx +2 -1
- package/ui/Agent/EmbedApproval.tsx +17 -0
- package/ui/Agent/RichInput.tsx +1 -1
- package/ui/Agent/index.ts +2 -0
- package/ui/Agent/useKeyboardFrame.ts +10 -0
- package/ui/Agent/useScreenActions.ts +45 -0
- package/ui/BottomSheet.tsx +82 -53
- package/ui/Constant/Doc.tsx +113 -60
- package/ui/Constant/index.ts +2 -2
- package/ui/Constant/schemaDoc.ts +86 -26
- package/ui/Dialog/LegacyModal.tsx +53 -52
- package/ui/Dialog/Modal.tsx +93 -49
- package/ui/Dialog/modalSurface.ts +61 -0
- package/ui/DragAction.tsx +1 -1
- package/ui/DraggableList.tsx +1 -1
- package/ui/Dropdown.tsx +4 -6
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/BottomInset.tsx +32 -16
- package/ui/Layout/BottomTab.tsx +42 -16
- package/ui/Layout/FrameSlotFallback.tsx +59 -0
- package/ui/Layout/Navbar.tsx +56 -15
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Layout/TopInset.tsx +22 -17
- package/ui/Link/Action.tsx +15 -5
- package/ui/Link/Anchor.tsx +25 -13
- package/ui/Link/activePath.ts +25 -0
- package/ui/Link/index.tsx +1 -1
- package/ui/Load/Pagination.tsx +1 -1
- package/ui/Load/Units.tsx +1 -1
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Modal.tsx +9 -0
- package/ui/Model/EditModal.tsx +16 -1
- package/ui/Model/New.tsx +25 -2
- package/ui/Model/NewWrapper_Client.tsx +2 -0
- package/ui/Model/ViewEditModal.tsx +25 -10
- package/ui/Model/ViewModal.tsx +16 -1
- package/ui/Model/newFormOrigin.ts +45 -0
- package/ui/Popconfirm.tsx +5 -7
- package/ui/Portal.tsx +4 -7
- package/ui/Reference/origin.ts +31 -0
- package/ui/ScreenNavigator.tsx +1 -1
- package/ui/Select.tsx +14 -14
- package/ui/Signal/Arg.tsx +2 -2
- package/ui/Signal/Doc.tsx +125 -63
- package/ui/Signal/Endpoint.tsx +11 -10
- package/ui/Signal/Listener.tsx +7 -10
- package/ui/Signal/Object.tsx +7 -14
- package/ui/Signal/Response.tsx +24 -24
- package/ui/Signal/RestApi.tsx +63 -52
- package/ui/Signal/WebSocket.tsx +65 -72
- package/ui/Signal/endpointEntries.ts +25 -0
- package/ui/Signal/index.ts +23 -6
- package/ui/Signal/signalText.ts +59 -0
- package/ui/System/CSR.tsx +41 -9
- package/ui/System/Client.tsx +37 -5
- package/ui/System/Common.tsx +76 -32
- package/ui/System/SSR.tsx +73 -31
- package/ui/Toast.tsx +1 -1
- package/ui/Tooltip.tsx +84 -30
- package/ui/index.ts +11 -1
- package/ui/overlayLayer.ts +25 -2
- package/ui/overlayPosition.ts +90 -0
- package/ui/recipe/inputRecipe.ts +8 -1
- package/ui/styles.css +152 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
- package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
- package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
- package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
- package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
- package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
- package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
- package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +60 -21
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
- package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
- package/vendor/@akanjs/native/packages/desktop/src/dispatcher.ts +13 -5
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
- package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
- package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
- package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
- package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +4 -0
- package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +12 -5
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
- package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
- package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
- package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
- package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
- package/vendor/use-agentic/AgentActivity.tsx +4 -2
- package/vendor/use-agentic/AgenticSurface.ts +3 -3
- package/vendor/use-agentic/ToolRunner.ts +8 -1
- package/vendor/use-agentic/types.ts +2 -0
- package/webkit/CsrRouteTable.ts +9 -1
- package/webkit/CsrStack.ts +9 -2
- package/webkit/EmbeddedLayout.tsx +77 -0
- package/webkit/McpAppAgent.ts +138 -0
- package/webkit/McpAppApprovals.ts +56 -0
- package/webkit/McpAppBridge.ts +291 -0
- package/webkit/McpAppView.ts +258 -0
- package/webkit/RenderLayer.tsx +20 -5
- package/webkit/bootCsr.tsx +54 -5
- package/webkit/index.ts +1 -0
- package/webkit/useCsrValues.ts +16 -10
- package/webkit/useFrameRuntime.ts +10 -1
- package/webkit/useLocation.ts +4 -3
- package/webkit/useVisualViewportFrame.tsx +52 -0
- package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
- package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
- package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
- package/types/ui/ServerPortal.d.ts +0 -15
- package/ui/Agent/useKeyboardInset.ts +0 -19
- package/ui/ServerPortal.tsx +0 -49
|
@@ -279,3 +279,52 @@ export function decideNavigation(
|
|
|
279
279
|
if (!topLevel) return FRAME_SCHEMES.includes(scheme) ? "load" : "drop";
|
|
280
280
|
return externalSchemes(extra).includes(scheme) ? "open" : "drop";
|
|
281
281
|
}
|
|
282
|
+
|
|
283
|
+
export interface UpdateDecisionInput {
|
|
284
|
+
/** The binary's keyIds, in `updates.publicKeys` order. */
|
|
285
|
+
ring: readonly string[];
|
|
286
|
+
/** keyIds state.json has revoked. */
|
|
287
|
+
revoked: readonly string[];
|
|
288
|
+
/** The envelope's keyId, whose signature over the manifest verified. */
|
|
289
|
+
signer: string;
|
|
290
|
+
revokeKeys: readonly string[];
|
|
291
|
+
resetSequence: boolean;
|
|
292
|
+
sequence: number;
|
|
293
|
+
/** The binary's own bundle (updates.json `embeddedSequence`). */
|
|
294
|
+
embedded: number;
|
|
295
|
+
/** The bundle running now, null for the binary's own. */
|
|
296
|
+
running: { sequence: number; keyId: string } | null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export type UpdateDecision =
|
|
300
|
+
| { kind: "untrusted" | "revoked" | "self-revoke" | "revoke-later" }
|
|
301
|
+
| { kind: "take" | "stale"; baseline: number; revoke: string[] };
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Whether a web bundle manifest is taken (updates protocol v2, vectors/updates.json). A key may revoke only keys listed
|
|
305
|
+
* before it, so a leaked online key — listed first — cannot revoke the offline spares that recover from it, and
|
|
306
|
+
* `resetSequence` counts only on a manifest that newly revokes a key, so replaying it cannot roll anyone back. A bundle a
|
|
307
|
+
* revoked key signed sets no bar, and revocations apply even when the release itself is not newer.
|
|
308
|
+
*/
|
|
309
|
+
export function updateDecision(input: UpdateDecisionInput): UpdateDecision {
|
|
310
|
+
const { ring, revoked, signer, revokeKeys } = input;
|
|
311
|
+
const index = ring.indexOf(signer);
|
|
312
|
+
if (index < 0) return { kind: "untrusted" };
|
|
313
|
+
if (revoked.includes(signer)) return { kind: "revoked" };
|
|
314
|
+
if (revokeKeys.includes(signer)) return { kind: "self-revoke" };
|
|
315
|
+
if (revokeKeys.some((id) => ring.indexOf(id) > index)) return { kind: "revoke-later" };
|
|
316
|
+
const revoke = ring.filter((id) => revokeKeys.includes(id) && !revoked.includes(id));
|
|
317
|
+
const running = input.running && ![...revoked, ...revoke].includes(input.running.keyId) ? input.running.sequence : 0;
|
|
318
|
+
const baseline = input.resetSequence && revoke.length ? input.embedded : Math.max(input.embedded, running);
|
|
319
|
+
return { kind: input.sequence > baseline ? "take" : "stale", baseline, revoke };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** One answer of updateDecision as the vectors write it. */
|
|
323
|
+
export const updateDecisionWire = (decision: UpdateDecision) =>
|
|
324
|
+
decision.kind === "take" || decision.kind === "stale"
|
|
325
|
+
? `${decision.kind}:${decision.baseline}:${decision.revoke.join(",")}`
|
|
326
|
+
: decision.kind;
|
|
327
|
+
|
|
328
|
+
/** The v2 envelope of a channel for one native API: one file per binary generation, so each takes its own fixes. */
|
|
329
|
+
export const updateManifestUrl = (url: string, platform: string, channel: string, nativeApi: string) =>
|
|
330
|
+
`${url}/${platform}/${channel}/${nativeApi}.json`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"about": "Web bundle updates, protocol v2 (architecture.md \"업데이트\"). keyIds: [raw Ed25519 public key in base64, its keyId: the first 8 bytes of its sha256 in hex]. decisions: [the binary's keyIds in updates.publicKeys order, keyIds state.json has revoked, the envelope's keyId, the manifest's revokeKeys, its resetSequence, its sequence, the binary's embeddedSequence, the running bundle as [sequence, keyId] or null for the app's own, the answer, why] for a manifest whose signature verified; the answer is untrusted | revoked | self-revoke | revoke-later, or take|stale:<the sequence it had to beat>:<keyIds it newly revokes, comma-separated>, revocations applying even to a stale manifest. urls: [updates.url, platform, channel, nativeApi, the envelope's URL].",
|
|
3
|
+
"keyIds": [
|
|
4
|
+
["BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=", "4bb06f8e4e3a7715"],
|
|
5
|
+
["11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=", "21fe31dfa154a261"],
|
|
6
|
+
["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "66687aadf862bd77"]
|
|
7
|
+
],
|
|
8
|
+
"decisions": [
|
|
9
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", [], false, 200, 100, null, "take:100:", "signed by the first key, nothing running but the app's own bundle"],
|
|
10
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "d4d4d4d4d4d4d4d4", [], false, 200, 100, null, "untrusted", "a key the binary does not carry"],
|
|
11
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], ["a1a1a1a1a1a1a1a1"], "a1a1a1a1a1a1a1a1", [], false, 200, 100, null, "revoked", "a key revoked earlier signs nothing"],
|
|
12
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "b2b2b2b2b2b2b2b2", ["b2b2b2b2b2b2b2b2"], false, 200, 100, null, "self-revoke", "a key never revokes itself"],
|
|
13
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", ["b2b2b2b2b2b2b2b2"], false, 200, 100, null, "revoke-later", "a key revokes only keys listed before it"],
|
|
14
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "b2b2b2b2b2b2b2b2", ["a1a1a1a1a1a1a1a1"], false, 300, 100, [500, "a1a1a1a1a1a1a1a1"], "take:100:a1a1a1a1a1a1a1a1", "the bundle a revoked key signed no longer sets the bar"],
|
|
15
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "b2b2b2b2b2b2b2b2", ["a1a1a1a1a1a1a1a1"], true, 300, 100, [500, "b2b2b2b2b2b2b2b2"], "take:100:a1a1a1a1a1a1a1a1", "resetSequence with a new revocation compares with the app's own bundle"],
|
|
16
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], ["a1a1a1a1a1a1a1a1"], "b2b2b2b2b2b2b2b2", ["a1a1a1a1a1a1a1a1"], true, 300, 100, [500, "b2b2b2b2b2b2b2b2"], "stale:500:", "replayed: nothing new revoked, so the reset does not apply"],
|
|
17
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "b2b2b2b2b2b2b2b2", ["a1a1a1a1a1a1a1a1", "d4d4d4d4d4d4d4d4"], false, 200, 100, null, "take:100:a1a1a1a1a1a1a1a1", "an id this binary does not carry is ignored"],
|
|
18
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", [], false, 100, 100, null, "stale:100:", "no newer than the app's own bundle"],
|
|
19
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", [], false, 150, 100, [150, "a1a1a1a1a1a1a1a1"], "stale:150:", "the bundle already running"],
|
|
20
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "c3c3c3c3c3c3c3c3", ["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2"], false, 120, 100, null, "take:100:a1a1a1a1a1a1a1a1,b2b2b2b2b2b2b2b2", "the last key revokes both before it"],
|
|
21
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "b2b2b2b2b2b2b2b2", ["a1a1a1a1a1a1a1a1", "a1a1a1a1a1a1a1a1"], false, 200, 100, null, "take:100:a1a1a1a1a1a1a1a1", "listed twice, revoked once"],
|
|
22
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", [], false, 160, 100, [150, "b2b2b2b2b2b2b2b2"], "take:150:", "another trusted key's bundle sets the bar"],
|
|
23
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], ["a1a1a1a1a1a1a1a1"], "c3c3c3c3c3c3c3c3", ["b2b2b2b2b2b2b2b2"], false, 200, 100, [900, "a1a1a1a1a1a1a1a1"], "take:100:b2b2b2b2b2b2b2b2", "a bundle of a key revoked before still runs this session but sets no bar"],
|
|
24
|
+
[["a1a1a1a1a1a1a1a1", "b2b2b2b2b2b2b2b2", "c3c3c3c3c3c3c3c3"], [], "a1a1a1a1a1a1a1a1", [], true, 300, 100, [500, "a1a1a1a1a1a1a1a1"], "stale:500:", "resetSequence without a revocation does nothing"],
|
|
25
|
+
[["a1a1a1a1a1a1a1a1"], [], "a1a1a1a1a1a1a1a1", [], false, 200, 100, null, "take:100:", "a ring of one"]
|
|
26
|
+
],
|
|
27
|
+
"urls": [
|
|
28
|
+
["https://asset.akamir.com/ota/inst1/pathA", "ios", "main", "0123456789abcdef", "https://asset.akamir.com/ota/inst1/pathA/ios/main/0123456789abcdef.json"],
|
|
29
|
+
["http://10.0.2.2:8790", "android", "pilot", "fedcba9876543210", "http://10.0.2.2:8790/android/pilot/fedcba9876543210.json"]
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -33,6 +33,12 @@ import type {
|
|
|
33
33
|
/** How long the window waits for one plugin's setup. */
|
|
34
34
|
export const SETUP_TIMEOUT_MS = 3000;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* How long the launch waits for a gate plugin's setup. A gate decides whether this launch runs at all, so passing it
|
|
38
|
+
* by on time starts what the losing launch must not: a second app's server on the same data (single-instance).
|
|
39
|
+
*/
|
|
40
|
+
export const GATE_TIMEOUT_MS = 30_000;
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* How long a page's call to a plugin whose setup is still running (it outlasted SETUP_TIMEOUT_MS)
|
|
38
44
|
* waits for it before it is answered INTERNAL with `retryable`.
|
|
@@ -189,6 +195,7 @@ export const COALESCE_MS = 100;
|
|
|
189
195
|
|
|
190
196
|
export interface DispatcherOptions {
|
|
191
197
|
setupTimeoutMs?: number;
|
|
198
|
+
gateTimeoutMs?: number;
|
|
192
199
|
setupWaitMs?: number;
|
|
193
200
|
/** The app's resolved capabilities from boot.json (PL-11). Absent: everything is allowed. */
|
|
194
201
|
acl?: ResolvedAcl | null;
|
|
@@ -301,18 +308,19 @@ export function createDispatcher(
|
|
|
301
308
|
};
|
|
302
309
|
|
|
303
310
|
const timeout = options.setupTimeoutMs ?? SETUP_TIMEOUT_MS;
|
|
311
|
+
const gateTimeout = options.gateTimeoutMs ?? GATE_TIMEOUT_MS;
|
|
304
312
|
/** Setups still running (plugin lifecycle rule: calls and $listen wait for them). */
|
|
305
313
|
const settingUp = new Map<string, Promise<unknown>>();
|
|
306
|
-
const setUp = async (plugin: DesktopPlugin) => {
|
|
314
|
+
const setUp = async (plugin: DesktopPlugin, limit = timeout) => {
|
|
307
315
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
308
316
|
try {
|
|
309
317
|
const done = Promise.resolve(plugin.setup?.(context(plugin)));
|
|
310
318
|
settingUp.set(plugin.id, done);
|
|
311
319
|
const forget = () => void settingUp.delete(plugin.id);
|
|
312
320
|
done.then(forget, forget);
|
|
313
|
-
const late = new Promise<"late">((resolve) => (timer = setTimeout(() => resolve("late"),
|
|
321
|
+
const late = new Promise<"late">((resolve) => (timer = setTimeout(() => resolve("late"), limit)));
|
|
314
322
|
if ((await Promise.race([done, late])) === "late")
|
|
315
|
-
console.warn(`[akan-native] ${plugin.id} setup took over ${
|
|
323
|
+
console.warn(`[akan-native] ${plugin.id} setup took over ${limit} ms; creating the window anyway`);
|
|
316
324
|
} catch (error) {
|
|
317
325
|
console.error(`[akan-native] ${plugin.id} setup failed`, error);
|
|
318
326
|
} finally {
|
|
@@ -322,10 +330,10 @@ export function createDispatcher(
|
|
|
322
330
|
|
|
323
331
|
const setups = (async () => {
|
|
324
332
|
for (const plugin of plugins.filter((p) => p.launchPhase === "gate")) {
|
|
325
|
-
await setUp(plugin);
|
|
333
|
+
await setUp(plugin, gateTimeout);
|
|
326
334
|
if (launch.exit !== undefined) return;
|
|
327
335
|
}
|
|
328
|
-
await Promise.all(plugins.filter((p) => p.launchPhase !== "gate").map(setUp));
|
|
336
|
+
await Promise.all(plugins.filter((p) => p.launchPhase !== "gate").map((plugin) => setUp(plugin)));
|
|
329
337
|
})();
|
|
330
338
|
const launched = setups.then(() => {
|
|
331
339
|
launching = false;
|
|
@@ -49,6 +49,7 @@ interface ShellConfig {
|
|
|
49
49
|
/** desktop.window: the main window from its first frame, unless the launch phase says otherwise. */
|
|
50
50
|
fullscreen?: boolean;
|
|
51
51
|
skipTaskbar?: boolean;
|
|
52
|
+
frameless?: boolean;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
const finite = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
@@ -62,6 +63,7 @@ export function launchBounds(window: LaunchWindow): LaunchWindow {
|
|
|
62
63
|
if (window.maximized === true) out.maximized = true;
|
|
63
64
|
if (typeof window.fullscreen === "boolean") out.fullscreen = window.fullscreen;
|
|
64
65
|
if (typeof window.skipTaskbar === "boolean") out.skipTaskbar = window.skipTaskbar;
|
|
66
|
+
if (typeof window.frameless === "boolean") out.frameless = window.frameless;
|
|
65
67
|
return out;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -154,6 +156,7 @@ export async function startMain(workerUrl: string): Promise<never> {
|
|
|
154
156
|
height: shell.height ?? 720,
|
|
155
157
|
fullscreen: shell.fullscreen === true,
|
|
156
158
|
skipTaskbar: shell.skipTaskbar === true,
|
|
159
|
+
frameless: shell.frameless === true,
|
|
157
160
|
...bounds,
|
|
158
161
|
appDir: paths.appDir,
|
|
159
162
|
initJs,
|
|
@@ -170,6 +170,8 @@ export interface LaunchWindow {
|
|
|
170
170
|
fullscreen?: boolean;
|
|
171
171
|
/** No taskbar button (Windows, Linux; macOS has none). */
|
|
172
172
|
skipTaskbar?: boolean;
|
|
173
|
+
/** No title bar or border. */
|
|
174
|
+
frameless?: boolean;
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
/** What the launch phase decided; sent to the main thread with "ready". */
|
|
@@ -20,8 +20,15 @@ export interface ServerManifest {
|
|
|
20
20
|
/** The file in resources/server that starts the server. */
|
|
21
21
|
entry: string;
|
|
22
22
|
env: Record<string, string>;
|
|
23
|
+
/** Crashes in a row before the launcher gives up, MAX_FAILURES unless named; "forever" never gives up. */
|
|
24
|
+
restart?: number | "forever";
|
|
25
|
+
/** The health route asked every `intervalMs` once the server is ready; `failures` missed in a row restart it. */
|
|
26
|
+
watchdog?: { intervalMs: number; failures: number };
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
/** Answered by the server's own process, solo or behind its gateway, without a database round trip. */
|
|
30
|
+
export const HEALTH_PATH = "/_akan/app/health";
|
|
31
|
+
|
|
25
32
|
/** Set by the launcher at every start, so desktop.server.env may not name them (cli lib/desktop-server.ts). */
|
|
26
33
|
export const LAUNCHER_ENV_KEYS = [
|
|
27
34
|
"BUN_BE_BUN",
|
|
@@ -138,6 +145,8 @@ export interface DesktopServerOptions {
|
|
|
138
145
|
stopGrace?: number;
|
|
139
146
|
restartDelay?(failures: number, everReady: boolean): number;
|
|
140
147
|
now?(): number;
|
|
148
|
+
/** Whether the server at `url` answers its health route; the watchdog's question. */
|
|
149
|
+
probe?(url: string, timeoutMs: number): Promise<boolean>;
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
export interface DesktopServer {
|
|
@@ -186,14 +195,28 @@ export function createServerStatus() {
|
|
|
186
195
|
export function readServerManifest(resources: string): ServerManifest | null {
|
|
187
196
|
const file = join(resources, "server.json");
|
|
188
197
|
if (!existsSync(file)) return null;
|
|
189
|
-
const raw = JSON.parse(readFileSync(file, "utf8")) as {
|
|
198
|
+
const raw = JSON.parse(readFileSync(file, "utf8")) as {
|
|
199
|
+
entry?: unknown;
|
|
200
|
+
env?: unknown;
|
|
201
|
+
restart?: unknown;
|
|
202
|
+
watchdog?: { intervalMs?: unknown; failures?: unknown } | null;
|
|
203
|
+
};
|
|
190
204
|
if (typeof raw.entry !== "string" || !raw.entry) throw new Error(`${file}: entry must name a file`);
|
|
191
205
|
const env = Object.fromEntries(
|
|
192
206
|
Object.entries((raw.env ?? {}) as Record<string, unknown>).filter(
|
|
193
207
|
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
194
208
|
),
|
|
195
209
|
);
|
|
196
|
-
|
|
210
|
+
const restart =
|
|
211
|
+
raw.restart === "forever" || (Number.isInteger(raw.restart) && (raw.restart as number) > 0)
|
|
212
|
+
? (raw.restart as number | "forever")
|
|
213
|
+
: undefined;
|
|
214
|
+
const { intervalMs, failures } = raw.watchdog ?? {};
|
|
215
|
+
const watchdog =
|
|
216
|
+
Number.isInteger(intervalMs) && (intervalMs as number) > 0 && Number.isInteger(failures) && (failures as number) > 0
|
|
217
|
+
? { intervalMs: intervalMs as number, failures: failures as number }
|
|
218
|
+
: undefined;
|
|
219
|
+
return { entry: raw.entry, env, ...(restart ? { restart } : {}), ...(watchdog ? { watchdog } : {}) };
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
export function serverArgv(execPath: string, resources: string, entry: string): string[] {
|
|
@@ -299,6 +322,16 @@ export function jwtSecret(dataDir: string): string {
|
|
|
299
322
|
return secret;
|
|
300
323
|
}
|
|
301
324
|
|
|
325
|
+
async function probeHealth(url: string, timeoutMs: number): Promise<boolean> {
|
|
326
|
+
try {
|
|
327
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
328
|
+
await response.body?.cancel().catch(() => {});
|
|
329
|
+
return response.ok;
|
|
330
|
+
} catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
302
335
|
export function restartDelay(failures: number, everReady = true): number {
|
|
303
336
|
return everReady ? Math.min(1000 * 2 ** (failures - 1), 30_000) : BOOT_RESTART_DELAY;
|
|
304
337
|
}
|
|
@@ -404,6 +437,8 @@ export function createDesktopServer(options: DesktopServerOptions): DesktopServe
|
|
|
404
437
|
const pickPort = options.freePort ?? loopbackPort;
|
|
405
438
|
const argv = serverArgv(options.execPath ?? process.execPath, options.resources, options.manifest.entry);
|
|
406
439
|
const grace = options.stopGrace ?? STOP_GRACE;
|
|
440
|
+
const maxFailures = options.manifest.restart === "forever" ? Infinity : (options.manifest.restart ?? MAX_FAILURES);
|
|
441
|
+
const probe = options.probe ?? probeHealth;
|
|
407
442
|
const logs = join(dataDir, "runtime", "logs");
|
|
408
443
|
const outputLog = join(logs, "server-output.log");
|
|
409
444
|
let outputSize = -1;
|
|
@@ -474,19 +509,46 @@ export function createDesktopServer(options: DesktopServerOptions): DesktopServe
|
|
|
474
509
|
|
|
475
510
|
const failed = (why: string) => {
|
|
476
511
|
failures++;
|
|
477
|
-
const
|
|
512
|
+
const count = Number.isFinite(maxFailures) ? `${failures} of ${maxFailures}` : `${failures}`;
|
|
513
|
+
const line = `[akan-native] the server ${why} (${count} in a row)`;
|
|
478
514
|
console.error(line);
|
|
479
515
|
record(line);
|
|
480
516
|
setState(everReady ? "restarting" : "starting");
|
|
481
|
-
if (failures >=
|
|
517
|
+
if (failures >= maxFailures)
|
|
482
518
|
return giveUp(
|
|
483
|
-
`The app's server stopped ${
|
|
519
|
+
`The app's server stopped ${maxFailures} times in a row and is not restarted again. Its logs are in ${logs}.`,
|
|
484
520
|
);
|
|
485
|
-
|
|
521
|
+
|
|
522
|
+
const paced = everReady || failures >= MAX_FAILURES;
|
|
523
|
+
restartTimer = setTimeout(() => void launch(), (options.restartDelay ?? restartDelay)(failures, paced));
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const watch = (child: ServerProcess) => {
|
|
527
|
+
const { watchdog } = options.manifest;
|
|
528
|
+
if (!watchdog) return () => {};
|
|
529
|
+
let missed = 0;
|
|
530
|
+
let asking = false;
|
|
531
|
+
const timer = setInterval(async () => {
|
|
532
|
+
if (asking || proc !== child || stopping) return;
|
|
533
|
+
asking = true;
|
|
534
|
+
const healthy = await probe(`http://127.0.0.1:${port}${HEALTH_PATH}`, Math.min(watchdog.intervalMs, 5000));
|
|
535
|
+
asking = false;
|
|
536
|
+
if (proc !== child || stopping) return;
|
|
537
|
+
missed = healthy ? 0 : missed + 1;
|
|
538
|
+
if (missed < watchdog.failures) return;
|
|
539
|
+
const line = `[akan-native] the server missed ${missed} health checks in a row; killing it to restart`;
|
|
540
|
+
console.error(line);
|
|
541
|
+
record(line);
|
|
542
|
+
clearInterval(timer);
|
|
543
|
+
child.kill("SIGKILL");
|
|
544
|
+
}, watchdog.intervalMs);
|
|
545
|
+
timer.unref?.();
|
|
546
|
+
return () => clearInterval(timer);
|
|
486
547
|
};
|
|
487
548
|
|
|
488
549
|
const run = () => {
|
|
489
550
|
let readyAt: number | null = null;
|
|
551
|
+
let unwatch = () => {};
|
|
490
552
|
const child = spawn(argv, {
|
|
491
553
|
cwd: dataDir,
|
|
492
554
|
env,
|
|
@@ -496,6 +558,7 @@ export function createDesktopServer(options: DesktopServerOptions): DesktopServe
|
|
|
496
558
|
if (type !== "ready" || readyAt !== null) return;
|
|
497
559
|
readyAt = now();
|
|
498
560
|
setState("up");
|
|
561
|
+
unwatch = watch(child);
|
|
499
562
|
if (!everReady) {
|
|
500
563
|
everReady = true;
|
|
501
564
|
settleReady(true);
|
|
@@ -513,6 +576,7 @@ export function createDesktopServer(options: DesktopServerOptions): DesktopServe
|
|
|
513
576
|
proc = child;
|
|
514
577
|
child.exited.then(
|
|
515
578
|
(code) => {
|
|
579
|
+
unwatch();
|
|
516
580
|
if (proc === child) proc = null;
|
|
517
581
|
child.killGroup?.(stopping ? "SIGKILL" : "SIGTERM");
|
|
518
582
|
if (stopping) return;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "external-view",
|
|
3
|
+
"apiVersion": 1,
|
|
4
|
+
"methods": [
|
|
5
|
+
"open",
|
|
6
|
+
"navigate",
|
|
7
|
+
"reload",
|
|
8
|
+
"close",
|
|
9
|
+
"list",
|
|
10
|
+
"setVisible",
|
|
11
|
+
"setBounds",
|
|
12
|
+
"evaluate",
|
|
13
|
+
"sendKey",
|
|
14
|
+
"setMuted",
|
|
15
|
+
"clearData",
|
|
16
|
+
"capture"
|
|
17
|
+
],
|
|
18
|
+
"events": ["load", "loadFailed", "gone", "consoleMessage"],
|
|
19
|
+
"web": null,
|
|
20
|
+
"desktop": "./src/desktop.ts",
|
|
21
|
+
"ios": null,
|
|
22
|
+
"android": null
|
|
23
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { mkdirSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { AkanNativeError } from "../../../packages/core/src/index.ts";
|
|
7
|
+
import { type DesktopContext, defineDesktopPlugin } from "../../../packages/desktop/src/plugin.ts";
|
|
8
|
+
import type {
|
|
9
|
+
ExternalViewApi,
|
|
10
|
+
ExternalViewBounds,
|
|
11
|
+
ExternalViewConsoleLevel,
|
|
12
|
+
ExternalViewEvents,
|
|
13
|
+
ExternalViewState,
|
|
14
|
+
} from "./index.ts";
|
|
15
|
+
|
|
16
|
+
const ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
17
|
+
const LEVELS: ExternalViewConsoleLevel[] = ["debug", "info", "warn", "error"];
|
|
18
|
+
/** consoleMessage per view: this many in CONSOLE_WINDOW_MS, the rest counted and reported with the next one. */
|
|
19
|
+
export const CONSOLE_BURST = 20;
|
|
20
|
+
export const CONSOLE_WINDOW_MS = 10_000;
|
|
21
|
+
|
|
22
|
+
function id(args: unknown): string {
|
|
23
|
+
const value = (args as { id?: unknown } | undefined)?.id;
|
|
24
|
+
if (typeof value !== "string" || !ID.test(value))
|
|
25
|
+
throw new AkanNativeError("INVALID_ARGS", "id must be 1-64 letters, digits, - or _");
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function text(args: unknown, name: string): string {
|
|
30
|
+
const value = (args as Record<string, unknown> | undefined)?.[name];
|
|
31
|
+
if (typeof value !== "string" || !value) throw new AkanNativeError("INVALID_ARGS", `${name} must be a string`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function bool(args: unknown, name: string): boolean {
|
|
36
|
+
const value = (args as Record<string, unknown> | undefined)?.[name];
|
|
37
|
+
if (typeof value !== "boolean") throw new AkanNativeError("INVALID_ARGS", `${name} must be a boolean`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function bounds(value: unknown): ExternalViewBounds | undefined {
|
|
42
|
+
if (value === undefined || value === "fill") return value;
|
|
43
|
+
const b = value as Record<string, unknown> | null;
|
|
44
|
+
const ok = !!b && ["x", "y", "width", "height"].every((k) => typeof b[k] === "number" && Number.isFinite(b[k]));
|
|
45
|
+
if (!ok || (b?.width as number) <= 0 || (b?.height as number) <= 0)
|
|
46
|
+
throw new AkanNativeError("INVALID_ARGS", 'bounds must be "fill" or { x, y, width, height } with a positive size');
|
|
47
|
+
return value as ExternalViewBounds;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function consoleLevel(value: unknown): ExternalViewConsoleLevel | null {
|
|
51
|
+
if (value === undefined || value === false) return null;
|
|
52
|
+
if (value === true) return "warn";
|
|
53
|
+
if (typeof value === "string" && (LEVELS as string[]).includes(value)) return value as ExternalViewConsoleLevel;
|
|
54
|
+
throw new AkanNativeError("INVALID_ARGS", `console must be a boolean or one of ${LEVELS.join(", ")}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const shell = (ctx: DesktopContext, op: string, args: Record<string, unknown>) => ctx.shell(`externalView.${op}`, args);
|
|
58
|
+
|
|
59
|
+
/** The engine's own text, which the shell sends on as a string: JSON, or not, as the engine had it. */
|
|
60
|
+
function parsed(value: unknown): unknown {
|
|
61
|
+
if (typeof value !== "string") return value;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(value) as unknown;
|
|
64
|
+
} catch {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function state(raw: unknown): ExternalViewState {
|
|
70
|
+
const { view, ...rest } = raw as { view: string } & Omit<ExternalViewState, "id">;
|
|
71
|
+
return { id: view, ...rest };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Chrome's virtual key code for a KeyboardEvent.code, which a trusted key event needs beside `key`. */
|
|
75
|
+
export function virtualKey(code: string): number {
|
|
76
|
+
const letter = /^Key([A-Z])$/.exec(code);
|
|
77
|
+
if (letter?.[1]) return letter[1].charCodeAt(0);
|
|
78
|
+
const digit = /^(?:Digit|Numpad)([0-9])$/.exec(code);
|
|
79
|
+
if (digit?.[1]) return (code.startsWith("Numpad") ? 96 : 48) + Number(digit[1]);
|
|
80
|
+
const fn = /^F([0-9]{1,2})$/.exec(code);
|
|
81
|
+
if (fn?.[1] && Number(fn[1]) >= 1 && Number(fn[1]) <= 24) return 111 + Number(fn[1]);
|
|
82
|
+
const named: Record<string, number> = {
|
|
83
|
+
Space: 32,
|
|
84
|
+
Enter: 13,
|
|
85
|
+
NumpadEnter: 13,
|
|
86
|
+
Escape: 27,
|
|
87
|
+
Tab: 9,
|
|
88
|
+
Backspace: 8,
|
|
89
|
+
Delete: 46,
|
|
90
|
+
ArrowLeft: 37,
|
|
91
|
+
ArrowUp: 38,
|
|
92
|
+
ArrowRight: 39,
|
|
93
|
+
ArrowDown: 40,
|
|
94
|
+
Home: 36,
|
|
95
|
+
End: 35,
|
|
96
|
+
PageUp: 33,
|
|
97
|
+
PageDown: 34,
|
|
98
|
+
Slash: 191,
|
|
99
|
+
Minus: 189,
|
|
100
|
+
Equal: 187,
|
|
101
|
+
Period: 190,
|
|
102
|
+
Comma: 188,
|
|
103
|
+
Semicolon: 186,
|
|
104
|
+
Quote: 222,
|
|
105
|
+
BracketLeft: 219,
|
|
106
|
+
BracketRight: 221,
|
|
107
|
+
Backslash: 220,
|
|
108
|
+
Backquote: 192,
|
|
109
|
+
};
|
|
110
|
+
const known = named[code];
|
|
111
|
+
if (known === undefined) throw new AkanNativeError("INVALID_ARGS", `code ${code} is not a key sendKey knows`);
|
|
112
|
+
return known;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface CdpConsoleCall {
|
|
116
|
+
type?: string;
|
|
117
|
+
args?: { value?: unknown; description?: string; unserializableValue?: string; type?: string }[];
|
|
118
|
+
stackTrace?: { callFrames?: { url?: string; lineNumber?: number }[] };
|
|
119
|
+
}
|
|
120
|
+
interface CdpException {
|
|
121
|
+
exceptionDetails?: {
|
|
122
|
+
text?: string;
|
|
123
|
+
url?: string;
|
|
124
|
+
lineNumber?: number;
|
|
125
|
+
exception?: { description?: string };
|
|
126
|
+
stackTrace?: CdpConsoleCall["stackTrace"];
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One console call or uncaught exception as a message, from the protocol event the shell passed on. */
|
|
131
|
+
export function consoleMessageOf(
|
|
132
|
+
method: string,
|
|
133
|
+
params: unknown,
|
|
134
|
+
): Omit<ExternalViewEvents["consoleMessage"], "id"> | null {
|
|
135
|
+
if (method === "Runtime.exceptionThrown") {
|
|
136
|
+
const details = (params as CdpException).exceptionDetails ?? {};
|
|
137
|
+
const frame = details.stackTrace?.callFrames?.[0];
|
|
138
|
+
return {
|
|
139
|
+
level: "error",
|
|
140
|
+
text: details.exception?.description ?? details.text ?? "Uncaught exception",
|
|
141
|
+
...(details.url || frame?.url ? { url: details.url || frame?.url } : {}),
|
|
142
|
+
...(typeof details.lineNumber === "number" ? { line: details.lineNumber + 1 } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (method !== "Runtime.consoleAPICalled") return null;
|
|
146
|
+
const call = params as CdpConsoleCall;
|
|
147
|
+
const level: ExternalViewConsoleLevel =
|
|
148
|
+
call.type === "error" || call.type === "assert"
|
|
149
|
+
? "error"
|
|
150
|
+
: call.type === "warning"
|
|
151
|
+
? "warn"
|
|
152
|
+
: call.type === "debug"
|
|
153
|
+
? "debug"
|
|
154
|
+
: "info";
|
|
155
|
+
const parts = (call.args ?? []).map((arg) =>
|
|
156
|
+
typeof arg.value === "string"
|
|
157
|
+
? arg.value
|
|
158
|
+
: String(arg.value ?? arg.unserializableValue ?? arg.description ?? arg.type),
|
|
159
|
+
);
|
|
160
|
+
const frame = call.stackTrace?.callFrames?.[0];
|
|
161
|
+
return {
|
|
162
|
+
level,
|
|
163
|
+
text: parts.join(" "),
|
|
164
|
+
...(frame?.url ? { url: frame.url } : {}),
|
|
165
|
+
...(typeof frame?.lineNumber === "number" ? { line: frame.lineNumber + 1 } : {}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Lets CONSOLE_BURST messages through per window of time, and counts what it held back for the next one. */
|
|
170
|
+
export function createConsoleCap(now: () => number = Date.now) {
|
|
171
|
+
const views = new Map<string, { since: number; sent: number; dropped: number }>();
|
|
172
|
+
return (view: string): { pass: boolean; dropped: number } => {
|
|
173
|
+
const t = now();
|
|
174
|
+
const entry = views.get(view) ?? { since: t, sent: 0, dropped: 0 };
|
|
175
|
+
if (t - entry.since >= CONSOLE_WINDOW_MS) Object.assign(entry, { since: t, sent: 0 });
|
|
176
|
+
views.set(view, entry);
|
|
177
|
+
if (entry.sent >= CONSOLE_BURST) {
|
|
178
|
+
entry.dropped++;
|
|
179
|
+
return { pass: false, dropped: 0 };
|
|
180
|
+
}
|
|
181
|
+
entry.sent++;
|
|
182
|
+
const dropped = entry.dropped;
|
|
183
|
+
entry.dropped = 0;
|
|
184
|
+
return { pass: true, dropped };
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function createDesktopExternalView() {
|
|
189
|
+
/** The console level each view was opened with: the shell sends every message, and this filters them. */
|
|
190
|
+
const levels = new Map<string, ExternalViewConsoleLevel>();
|
|
191
|
+
|
|
192
|
+
return defineDesktopPlugin<ExternalViewApi, ExternalViewEvents>({
|
|
193
|
+
id: "external-view",
|
|
194
|
+
methods: {
|
|
195
|
+
async open(args, ctx) {
|
|
196
|
+
const view = id(args);
|
|
197
|
+
const url = text(args, "url");
|
|
198
|
+
const allowOrigins = (args as { allowOrigins?: unknown }).allowOrigins;
|
|
199
|
+
if (!Array.isArray(allowOrigins) || !allowOrigins.length || allowOrigins.some((o) => typeof o !== "string"))
|
|
200
|
+
throw new AkanNativeError("INVALID_ARGS", "allowOrigins must be a list of origins");
|
|
201
|
+
const level = consoleLevel(args.console);
|
|
202
|
+
const opened = state(
|
|
203
|
+
await shell(ctx, "open", {
|
|
204
|
+
view,
|
|
205
|
+
url,
|
|
206
|
+
allowOrigins,
|
|
207
|
+
...(args.bounds !== undefined ? { bounds: bounds(args.bounds) } : {}),
|
|
208
|
+
...(typeof args.initScript === "string" ? { initScript: args.initScript } : {}),
|
|
209
|
+
muted: args.muted === true,
|
|
210
|
+
autoplay: args.autoplay !== false,
|
|
211
|
+
console: level !== null,
|
|
212
|
+
...(args.window !== undefined ? { window: args.window } : {}),
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
if (level) levels.set(view, level);
|
|
216
|
+
else levels.delete(view);
|
|
217
|
+
return opened;
|
|
218
|
+
},
|
|
219
|
+
async navigate(args, ctx) {
|
|
220
|
+
await shell(ctx, "navigate", { view: id(args), url: text(args, "url") });
|
|
221
|
+
},
|
|
222
|
+
async reload(args, ctx) {
|
|
223
|
+
await shell(ctx, "reload", { view: id(args) });
|
|
224
|
+
},
|
|
225
|
+
async close(args, ctx) {
|
|
226
|
+
const view = id(args);
|
|
227
|
+
await shell(ctx, "close", { view });
|
|
228
|
+
levels.delete(view);
|
|
229
|
+
},
|
|
230
|
+
async list(_args, ctx) {
|
|
231
|
+
return ((await shell(ctx, "list", {})) as unknown[]).map(state);
|
|
232
|
+
},
|
|
233
|
+
async setVisible(args, ctx) {
|
|
234
|
+
await shell(ctx, "setVisible", { view: id(args), visible: bool(args, "visible") });
|
|
235
|
+
},
|
|
236
|
+
async setBounds(args, ctx) {
|
|
237
|
+
await shell(ctx, "setBounds", { view: id(args), bounds: bounds(args.bounds) ?? "fill" });
|
|
238
|
+
},
|
|
239
|
+
async evaluate(args, ctx) {
|
|
240
|
+
return { value: parsed(await shell(ctx, "evaluate", { view: id(args), script: text(args, "script") })) };
|
|
241
|
+
},
|
|
242
|
+
async sendKey(args, ctx) {
|
|
243
|
+
const view = id(args);
|
|
244
|
+
const key = text(args, "key");
|
|
245
|
+
const code = text(args, "code");
|
|
246
|
+
const vk = virtualKey(code);
|
|
247
|
+
const base = { key, code, windowsVirtualKeyCode: vk, nativeVirtualKeyCode: vk };
|
|
248
|
+
|
|
249
|
+
const typed = key.length === 1 ? { text: key, unmodifiedText: key } : {};
|
|
250
|
+
await shell(ctx, "cdp", {
|
|
251
|
+
view,
|
|
252
|
+
method: "Input.dispatchKeyEvent",
|
|
253
|
+
params: { type: "keyDown", ...base, ...typed },
|
|
254
|
+
});
|
|
255
|
+
await shell(ctx, "cdp", { view, method: "Input.dispatchKeyEvent", params: { type: "keyUp", ...base } });
|
|
256
|
+
},
|
|
257
|
+
async setMuted(args, ctx) {
|
|
258
|
+
await shell(ctx, "setMuted", { view: id(args), muted: bool(args, "muted") });
|
|
259
|
+
},
|
|
260
|
+
async clearData(args, ctx) {
|
|
261
|
+
await shell(ctx, "clearData", { view: id(args) });
|
|
262
|
+
},
|
|
263
|
+
async capture(args, ctx) {
|
|
264
|
+
const view = id(args);
|
|
265
|
+
const shot = parsed(
|
|
266
|
+
await shell(ctx, "cdp", { view, method: "Page.captureScreenshot", params: { format: "png" } }),
|
|
267
|
+
) as { data?: string } | null;
|
|
268
|
+
if (!shot?.data) throw new AkanNativeError("INTERNAL", "the view answered no screenshot");
|
|
269
|
+
const dir = join(tmpdir(), "akan-native-external-view");
|
|
270
|
+
mkdirSync(dir, { recursive: true });
|
|
271
|
+
const file = join(dir, `${view}-${Date.now()}.png`);
|
|
272
|
+
const bytes = Buffer.from(shot.data, "base64");
|
|
273
|
+
await Bun.write(file, bytes);
|
|
274
|
+
|
|
275
|
+
const size = bytes.length >= 24 ? { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) } : {};
|
|
276
|
+
return { ...ctx.registerFile(file, "image/png"), ...size };
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
events: {
|
|
280
|
+
load: (emit, ctx) =>
|
|
281
|
+
ctx.onNativeEvent("externalView", (e) => {
|
|
282
|
+
if (e.event !== "load") return;
|
|
283
|
+
emit(
|
|
284
|
+
{ id: e.view as string, state: e.state as "started" | "finished", url: e.url as string },
|
|
285
|
+
{ window: e.window as number },
|
|
286
|
+
);
|
|
287
|
+
}),
|
|
288
|
+
loadFailed: (emit, ctx) =>
|
|
289
|
+
ctx.onNativeEvent("externalView", (e) => {
|
|
290
|
+
if (e.event !== "loadFailed") return;
|
|
291
|
+
emit(
|
|
292
|
+
{ id: e.view as string, url: e.url as string, errorCode: e.errorCode as string },
|
|
293
|
+
{ window: e.window as number },
|
|
294
|
+
);
|
|
295
|
+
}),
|
|
296
|
+
gone: (emit, ctx) =>
|
|
297
|
+
ctx.onNativeEvent("externalView", (e) => {
|
|
298
|
+
if (e.event !== "gone") return;
|
|
299
|
+
emit(
|
|
300
|
+
{ id: e.view as string, reason: e.reason as string, restarts: e.restarts as number },
|
|
301
|
+
{ window: e.window as number },
|
|
302
|
+
);
|
|
303
|
+
}),
|
|
304
|
+
consoleMessage: (emit, ctx) => {
|
|
305
|
+
const cap = createConsoleCap();
|
|
306
|
+
return ctx.onNativeEvent("externalView", (e) => {
|
|
307
|
+
if (e.event !== "cdp") return;
|
|
308
|
+
const view = e.view as string;
|
|
309
|
+
const min = levels.get(view);
|
|
310
|
+
const message = min ? consoleMessageOf(e.method as string, e.params) : null;
|
|
311
|
+
if (!min || !message || LEVELS.indexOf(message.level) < LEVELS.indexOf(min)) return;
|
|
312
|
+
const { pass, dropped } = cap(view);
|
|
313
|
+
if (!pass) return;
|
|
314
|
+
emit({ id: view, ...message, ...(dropped ? { dropped } : {}) }, { window: e.window as number });
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export default createDesktopExternalView();
|