akanjs 3.0.2 → 3.1.0-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/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 +32 -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 +8 -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/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/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 +40 -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/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/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 +3 -3
- 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 +7 -0
- 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/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/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/types.d.ts +2 -0
- package/types/webkit/CsrStack.d.ts +1 -1
- 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/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 +20 -14
- package/ui/Layout/BottomTab.tsx +42 -16
- package/ui/Layout/FrameSlotFallback.tsx +59 -0
- package/ui/Layout/Navbar.tsx +37 -11
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Layout/TopInset.tsx +15 -14
- 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 +9 -0
- 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 +2 -7
- 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 +34 -8
- package/ui/System/Client.tsx +36 -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 +25 -11
- 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/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/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/ToolRunner.ts +8 -1
- package/vendor/use-agentic/types.ts +2 -0
- package/webkit/CsrRouteTable.ts +1 -0
- package/webkit/CsrStack.ts +9 -2
- 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/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
|
@@ -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();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { definePlugin, type FileRef } from "../../../packages/core/src/index.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A third-party page shown first-party inside an app window, for signage: a scoreboard, a menu board. The view is a
|
|
5
|
+
* webview of its own laid over the window's page, which cannot draw over it — hide the view to show the page.
|
|
6
|
+
*
|
|
7
|
+
* Nothing of the app reaches the view: no bridge, no init script of the app, no app scheme. It keeps its own browser
|
|
8
|
+
* profile (cookies, storage, cache), so `clearData` never touches the app's, and it navigates only to `allowOrigins`;
|
|
9
|
+
* popups, downloads and device permissions are refused. Listing the plugin in `native.plugins` is what lets an app
|
|
10
|
+
* open one.
|
|
11
|
+
*
|
|
12
|
+
* Windows has every method. macOS (14 or later, for its separate profile) has all but `sendKey`, `setMuted` and
|
|
13
|
+
* `capture`, and sends no `loadFailed` or `consoleMessage`. Linux, iOS and Android: UNSUPPORTED.
|
|
14
|
+
*/
|
|
15
|
+
export type ExternalViewBounds = "fill" | { x: number; y: number; width: number; height: number };
|
|
16
|
+
|
|
17
|
+
export type ExternalViewConsoleLevel = "debug" | "info" | "warn" | "error";
|
|
18
|
+
|
|
19
|
+
export interface ExternalViewOpenOptions {
|
|
20
|
+
/** Names the view in every later call: letters, digits, `-` and `_`. */
|
|
21
|
+
id: string;
|
|
22
|
+
url: string;
|
|
23
|
+
/** The origins it may navigate to (`https://host[:port]`); `url` must be on one. */
|
|
24
|
+
allowOrigins: string[];
|
|
25
|
+
/** Logical pixels in the window's content area; "fill" (default) follows the window's size. */
|
|
26
|
+
bounds?: ExternalViewBounds;
|
|
27
|
+
/** Runs at the start of every top document, before the page's own scripts; read what it keeps with `evaluate`. */
|
|
28
|
+
initScript?: string;
|
|
29
|
+
muted?: boolean;
|
|
30
|
+
/** Media plays with sound without a gesture first (default true): nobody taps a signage screen. */
|
|
31
|
+
autoplay?: boolean;
|
|
32
|
+
/** Sends the page's console as `consoleMessage` from this level up (`true` is "warn"); off by default. */
|
|
33
|
+
console?: boolean | ExternalViewConsoleLevel;
|
|
34
|
+
/** The window it is laid over; the calling page's window by default. */
|
|
35
|
+
window?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ExternalViewState {
|
|
39
|
+
id: string;
|
|
40
|
+
window: number;
|
|
41
|
+
url: string;
|
|
42
|
+
visible: boolean;
|
|
43
|
+
/** Times its web process ended and the view loaded its page again. */
|
|
44
|
+
restarts: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ExternalViewApi {
|
|
48
|
+
/** Opens the view, or brings an open one with this id to these options (a page that mounts again finds it). */
|
|
49
|
+
open(args: ExternalViewOpenOptions): Promise<ExternalViewState>;
|
|
50
|
+
/** Rejects NOT_ALLOWED for a URL on none of its `allowOrigins`. */
|
|
51
|
+
navigate(args: { id: string; url: string }): Promise<void>;
|
|
52
|
+
reload(args: { id: string }): Promise<void>;
|
|
53
|
+
close(args: { id: string }): Promise<void>;
|
|
54
|
+
list(): Promise<ExternalViewState[]>;
|
|
55
|
+
setVisible(args: { id: string; visible: boolean }): Promise<void>;
|
|
56
|
+
setBounds(args: { id: string; bounds: ExternalViewBounds }): Promise<void>;
|
|
57
|
+
/** The script's completion value; a promise is not awaited. An exception answers `null`: catch it in the script. */
|
|
58
|
+
evaluate(args: { id: string; script: string }): Promise<{ value: unknown }>;
|
|
59
|
+
/** A trusted key press (`{ key: "z", code: "KeyZ" }`), whether or not the view has the focus. Windows only. */
|
|
60
|
+
sendKey(args: { id: string; key: string; code: string }): Promise<void>;
|
|
61
|
+
/** The view's own sound only; the system volume is the volume plugin's. Windows only. */
|
|
62
|
+
setMuted(args: { id: string; muted: boolean }): Promise<void>;
|
|
63
|
+
/** Clears the view's cookies, storage and cache; the app's own stay. */
|
|
64
|
+
clearData(args: { id: string }): Promise<void>;
|
|
65
|
+
/** A PNG of the view's content, not of the screen. Windows only. */
|
|
66
|
+
capture(args: { id: string }): Promise<FileRef & { width?: number; height?: number }>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ExternalViewEvents {
|
|
70
|
+
load: { id: string; state: "started" | "finished"; url: string };
|
|
71
|
+
/**
|
|
72
|
+
* A navigation that failed. `errorCode`: `http<status>` for a server's 4xx or 5xx page, else WebView2's own
|
|
73
|
+
* (`hostNameNotResolved`, `cannotConnect`, `timeout`, `certificate`, `disconnected`, …; a refused connection is
|
|
74
|
+
* `unknown`). Windows only.
|
|
75
|
+
*/
|
|
76
|
+
loadFailed: { id: string; url: string; errorCode: string };
|
|
77
|
+
/** Its web process ended; the view loads its page again by itself, sooner the first times. */
|
|
78
|
+
gone: { id: string; reason: string; restarts: number };
|
|
79
|
+
/** `dropped`: messages left out before this one, past the rate cap. Windows only. */
|
|
80
|
+
consoleMessage: {
|
|
81
|
+
id: string;
|
|
82
|
+
level: ExternalViewConsoleLevel;
|
|
83
|
+
text: string;
|
|
84
|
+
url?: string;
|
|
85
|
+
line?: number;
|
|
86
|
+
dropped?: number;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const externalView = definePlugin<ExternalViewApi, ExternalViewEvents>("external-view", {
|
|
91
|
+
methods: [
|
|
92
|
+
"open",
|
|
93
|
+
"navigate",
|
|
94
|
+
"reload",
|
|
95
|
+
"close",
|
|
96
|
+
"list",
|
|
97
|
+
"setVisible",
|
|
98
|
+
"setBounds",
|
|
99
|
+
"evaluate",
|
|
100
|
+
"sendKey",
|
|
101
|
+
"setMuted",
|
|
102
|
+
"clearData",
|
|
103
|
+
"capture",
|
|
104
|
+
],
|
|
105
|
+
events: ["load", "loadFailed", "gone", "consoleMessage"],
|
|
106
|
+
});
|