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
|
@@ -11,12 +11,18 @@ import { akanNativeHome } from "./toolchains.ts";
|
|
|
11
11
|
export interface UpdatesConfig {
|
|
12
12
|
/** Base URL of the update files, e.g. "https://example.com/updates". http:// only for localhost / 10.0.2.2 in dev builds. */
|
|
13
13
|
url: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Raw 32-byte Ed25519 public keys, base64 (`akan-native update keygen` prints one): the keys a phone binary trusts, the
|
|
16
|
+
* online signing key first and offline spares after it, since a key may revoke only keys listed before it. A desktop
|
|
17
|
+
* app trusts the first.
|
|
18
|
+
*/
|
|
19
|
+
publicKeys: string[];
|
|
16
20
|
/** Manifest name under <platform>/. Default "production". */
|
|
17
21
|
channel: string;
|
|
18
22
|
/** How long a newly applied bundle has to call notifyReady() before it is rolled back, ms. Default 10000. */
|
|
19
23
|
readyTimeout: number;
|
|
24
|
+
/** Where a phone posts what became of each release (downloaded, applied, confirmed, rolled back, failed); null: no reports. */
|
|
25
|
+
reportUrl: string | null;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export interface UpdateFile {
|
|
@@ -35,7 +41,8 @@ export function hostArch(): DesktopArch {
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export interface UpdateManifest {
|
|
38
|
-
|
|
44
|
+
/** 2 for a phone's web bundle, which travels in an UpdateEnvelope; 1 for a desktop app. */
|
|
45
|
+
schema: 1 | 2;
|
|
39
46
|
kind: "web" | "app";
|
|
40
47
|
app: string;
|
|
41
48
|
platform: Platform;
|
|
@@ -60,6 +67,19 @@ export interface UpdateManifest {
|
|
|
60
67
|
patches?: { from: string; url: string; sha256: string; size: number }[];
|
|
61
68
|
/** App updates: whether the app carries a server; an installed app refuses a release that differs from itself. */
|
|
62
69
|
server?: boolean;
|
|
70
|
+
/** Web bundles: keyIds the phone stops trusting for good; each listed before the signer's key in its updates.publicKeys. */
|
|
71
|
+
revokeKeys?: string[];
|
|
72
|
+
/** Web bundles: with a new revocation, compare this release with the binary's own bundle instead of the running one. */
|
|
73
|
+
resetSequence?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A phone's `<platform>/<channel>/<nativeApi>.json`: one object, so a new manifest never meets an old signature. */
|
|
77
|
+
export interface UpdateEnvelope {
|
|
78
|
+
/** The manifest's exact bytes, base64. */
|
|
79
|
+
manifest: string;
|
|
80
|
+
keyId: string;
|
|
81
|
+
/** Ed25519 over the manifest bytes, base64. */
|
|
82
|
+
signature: string;
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
/** Every file of a web bundle as the app serves it: index.html with the init script and CSP, public/, env.runtime.json. */
|
|
@@ -106,7 +126,7 @@ export function webManifest(release: {
|
|
|
106
126
|
files: UpdateFile[];
|
|
107
127
|
}): UpdateManifest {
|
|
108
128
|
return {
|
|
109
|
-
schema:
|
|
129
|
+
schema: 2,
|
|
110
130
|
kind: "web",
|
|
111
131
|
app: release.app.id,
|
|
112
132
|
platform: release.platform,
|
|
@@ -122,15 +142,15 @@ export function webManifest(release: {
|
|
|
122
142
|
|
|
123
143
|
/** A channel is a file name in the release folder and a string an app's config compares: lowercase, no separators. */
|
|
124
144
|
const CHANNEL_NAME = /^[a-z0-9][a-z0-9._-]{0,40}$/;
|
|
125
|
-
/**
|
|
126
|
-
const RESERVED_CHANNELS = ["bundle", "manifest.template", "compat"];
|
|
145
|
+
/** These would land on what an unsigned phone update (packUpdate) writes to the same folder, or on its `files/`. */
|
|
146
|
+
const RESERVED_CHANNELS = ["bundle", "manifest.template", "compat", "pack", "files", "app"];
|
|
127
147
|
|
|
128
148
|
/** Refuses a channel no app could be configured for: it would be written but never taken. */
|
|
129
149
|
export function assertChannel(channel: string): void {
|
|
130
150
|
if (!CHANNEL_NAME.test(channel))
|
|
131
151
|
throw new CliError(`channel "${channel}" is not a short lowercase name like "production" or "pilot"`);
|
|
132
152
|
if (RESERVED_CHANNELS.includes(channel))
|
|
133
|
-
throw new CliError(`channel "${channel}"
|
|
153
|
+
throw new CliError(`channel "${channel}" is a name the update folder already uses: name another`);
|
|
134
154
|
}
|
|
135
155
|
|
|
136
156
|
export function validateUpdates(raw: unknown, problems: string[]): UpdatesConfig | null {
|
|
@@ -140,10 +160,10 @@ export function validateUpdates(raw: unknown, problems: string[]): UpdatesConfig
|
|
|
140
160
|
return null;
|
|
141
161
|
}
|
|
142
162
|
const r = raw as Record<string, unknown>;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
163
|
+
|
|
164
|
+
if (typeof r.url !== "string" || !/^https?:\/\/[^/?#]+[^?#]*$/.test(r.url))
|
|
165
|
+
problems.push("updates.url must be an http(s) URL without a query or fragment");
|
|
166
|
+
const publicKeys = updatePublicKeys(r, problems);
|
|
147
167
|
if (
|
|
148
168
|
r.channel !== undefined &&
|
|
149
169
|
(typeof r.channel !== "string" || !CHANNEL_NAME.test(r.channel) || RESERVED_CHANNELS.includes(r.channel))
|
|
@@ -151,18 +171,56 @@ export function validateUpdates(raw: unknown, problems: string[]): UpdatesConfig
|
|
|
151
171
|
problems.push('updates.channel must be a short name like "production"');
|
|
152
172
|
if (r.readyTimeout !== undefined && (typeof r.readyTimeout !== "number" || r.readyTimeout < 1000))
|
|
153
173
|
problems.push("updates.readyTimeout must be a number of milliseconds >= 1000");
|
|
174
|
+
const reportUrl = updateReportUrl(r.report, problems);
|
|
154
175
|
for (const key of Object.keys(r))
|
|
155
|
-
if (!["url", "publicKey", "channel", "readyTimeout"].includes(key))
|
|
176
|
+
if (!["url", "publicKey", "publicKeys", "channel", "readyTimeout", "report"].includes(key))
|
|
156
177
|
problems.push(`updates.${key} is not supported`);
|
|
157
|
-
if (typeof r.url !== "string" ||
|
|
178
|
+
if (typeof r.url !== "string" || !publicKeys) return null;
|
|
158
179
|
return {
|
|
159
180
|
url: r.url.replace(/\/+$/, ""),
|
|
160
|
-
|
|
181
|
+
publicKeys,
|
|
161
182
|
channel: (r.channel as string | undefined) ?? "production",
|
|
162
183
|
readyTimeout: (r.readyTimeout as number | undefined) ?? 10_000,
|
|
184
|
+
reportUrl,
|
|
163
185
|
};
|
|
164
186
|
}
|
|
165
187
|
|
|
188
|
+
/** `report: { url }`, posted to as it is: the address is the receiver's, a path of its own included. */
|
|
189
|
+
function updateReportUrl(raw: unknown, problems: string[]): string | null {
|
|
190
|
+
if (raw === undefined) return null;
|
|
191
|
+
const url = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as Record<string, unknown>).url : undefined;
|
|
192
|
+
if (typeof url !== "string" || !/^https?:\/\/[^/?#]+[^#]*$/.test(url) || Object.keys(raw as object).length !== 1) {
|
|
193
|
+
problems.push("updates.report must be { url } with an http(s) URL");
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return url.replace(/\/+$/, "");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** `publicKeys` (1–3, no repeats) or the one-key `publicKey`, never both. */
|
|
200
|
+
function updatePublicKeys(r: Record<string, unknown>, problems: string[]): string[] | null {
|
|
201
|
+
if (r.publicKey !== undefined && r.publicKeys !== undefined) {
|
|
202
|
+
problems.push("updates takes publicKey or publicKeys, not both");
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const keys = r.publicKeys ?? (r.publicKey === undefined ? undefined : [r.publicKey]);
|
|
206
|
+
const isKey = (key: unknown) => typeof key === "string" && Buffer.from(key, "base64").length === 32;
|
|
207
|
+
if (!Array.isArray(keys) || keys.length < 1 || keys.length > 3 || !keys.every(isKey)) {
|
|
208
|
+
problems.push(
|
|
209
|
+
"updates.publicKeys must be 1 to 3 base64 Ed25519 public keys (akan-native update keygen), or publicKey one",
|
|
210
|
+
);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (new Set(keys.map(keyIdOf)).size !== keys.length) {
|
|
214
|
+
problems.push("updates.publicKeys lists a key twice");
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return keys as string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** What an envelope names its signer by: the first 8 bytes of the raw public key's SHA-256, in hex. */
|
|
221
|
+
export const keyIdOf = (publicKey: string) =>
|
|
222
|
+
createHash("sha256").update(Buffer.from(publicKey, "base64")).digest().subarray(0, 8).toString("hex");
|
|
223
|
+
|
|
166
224
|
/** Where the signing key of an app lives: AKAN_NATIVE_UPDATE_KEY (a file path), else ~/.akan/native/keys/<app id>.update.key. */
|
|
167
225
|
export function updateKeyPath(appId: string, env: Record<string, string | undefined> = process.env): string {
|
|
168
226
|
return env.AKAN_NATIVE_UPDATE_KEY ?? join(akanNativeHome(env), "keys", `${appId}.update.key`);
|
|
@@ -189,16 +247,60 @@ function publicKeyOf(privateKey: ReturnType<typeof createPrivateKey>): string {
|
|
|
189
247
|
return der.subarray(der.length - 32).toString("base64");
|
|
190
248
|
}
|
|
191
249
|
|
|
192
|
-
/** The key at `keyPath
|
|
193
|
-
export function signingKey(keyPath: string,
|
|
250
|
+
/** The key at `keyPath` and where it sits in `publicKeys`, refused unless the apps were built to trust it. */
|
|
251
|
+
export function signingKey(keyPath: string, publicKeys: readonly string[]) {
|
|
194
252
|
const key = readPrivateKey(keyPath);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
253
|
+
const publicKey = publicKeyOf(key);
|
|
254
|
+
const index = publicKeys.indexOf(publicKey);
|
|
255
|
+
if (index < 0) throw new CliError(`the key at ${keyPath} is not one of updates.publicKeys in akan-native.config.ts`);
|
|
256
|
+
return { key, keyId: keyIdOf(publicKey), index };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** A desktop app's base64 signature (protocol v1), with the key the app trusts: the first of updates.publicKeys. */
|
|
260
|
+
export function signManifest(bytes: Uint8Array, keyPath: string, publicKeys: readonly string[]): string {
|
|
261
|
+
const { key, index } = signingKey(keyPath, publicKeys);
|
|
262
|
+
if (index !== 0)
|
|
263
|
+
throw new CliError(
|
|
264
|
+
`a desktop app trusts the first of updates.publicKeys only, and the key at ${keyPath} is not it`,
|
|
265
|
+
);
|
|
266
|
+
return sign(null, bytes, key).toString("base64");
|
|
198
267
|
}
|
|
199
268
|
|
|
200
|
-
|
|
201
|
-
|
|
269
|
+
/** A phone's envelope (protocol v2) for the manifest's exact bytes, signed with any key of `publicKeys`. */
|
|
270
|
+
export function sealManifest(bytes: Uint8Array, keyPath: string, publicKeys: readonly string[]): UpdateEnvelope {
|
|
271
|
+
const { key, keyId } = signingKey(keyPath, publicKeys);
|
|
272
|
+
return {
|
|
273
|
+
manifest: Buffer.from(bytes).toString("base64"),
|
|
274
|
+
keyId,
|
|
275
|
+
signature: sign(null, bytes, key).toString("base64"),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The manifest bytes of an envelope, verified as a phone verifies it against `publicKeys` (minus `revoked`). What it
|
|
281
|
+
* then decides (revocation, sequence) is AkanNativeKernel.updateDecision, kernel.ts updateDecision.
|
|
282
|
+
*/
|
|
283
|
+
export function openEnvelope(
|
|
284
|
+
text: string,
|
|
285
|
+
publicKeys: readonly string[],
|
|
286
|
+
revoked: readonly string[] = [],
|
|
287
|
+
): { manifest: Uint8Array; keyId: string } {
|
|
288
|
+
let envelope: Partial<UpdateEnvelope>;
|
|
289
|
+
try {
|
|
290
|
+
envelope = JSON.parse(text) as Partial<UpdateEnvelope>;
|
|
291
|
+
} catch {
|
|
292
|
+
throw new CliError("the update envelope is not JSON");
|
|
293
|
+
}
|
|
294
|
+
const { manifest, keyId, signature } = envelope;
|
|
295
|
+
if (typeof manifest !== "string" || typeof keyId !== "string" || typeof signature !== "string")
|
|
296
|
+
throw new CliError("the update envelope needs manifest, keyId and signature");
|
|
297
|
+
const publicKey = publicKeys.find((key) => keyIdOf(key) === keyId);
|
|
298
|
+
if (!publicKey || revoked.includes(keyId))
|
|
299
|
+
throw new CliError(`the update envelope is signed by key ${keyId}, which is not trusted`);
|
|
300
|
+
const bytes = Buffer.from(manifest, "base64");
|
|
301
|
+
if (!verifyManifest(bytes, signature, publicKey))
|
|
302
|
+
throw new CliError("the update envelope's signature does not verify");
|
|
303
|
+
return { manifest: new Uint8Array(bytes), keyId };
|
|
202
304
|
}
|
|
203
305
|
|
|
204
306
|
/** Verifies like the apps do: raw public key, base64 signature over the exact manifest bytes. */
|
|
@@ -226,17 +328,51 @@ export function updatesResource(
|
|
|
226
328
|
platform: Platform,
|
|
227
329
|
nativeApi: string | undefined,
|
|
228
330
|
dev: boolean,
|
|
331
|
+
embeddedSequence = Math.floor(Date.now() / 1000),
|
|
229
332
|
): string {
|
|
230
333
|
return JSON.stringify({
|
|
231
334
|
app: app.id,
|
|
232
335
|
version: app.version,
|
|
233
336
|
platform,
|
|
234
337
|
nativeApi: nativeApi ?? null,
|
|
235
|
-
embeddedSequence
|
|
338
|
+
embeddedSequence,
|
|
236
339
|
dev,
|
|
237
340
|
url: updates.url,
|
|
238
|
-
|
|
341
|
+
protocol: platform === "ios" || platform === "android" ? 2 : 1,
|
|
342
|
+
publicKeys: updates.publicKeys,
|
|
343
|
+
|
|
344
|
+
publicKey: updates.publicKeys[0],
|
|
239
345
|
channel: updates.channel,
|
|
240
346
|
readyTimeout: updates.readyTimeout,
|
|
347
|
+
...(updates.reportUrl && (platform === "ios" || platform === "android") ? { reportUrl: updates.reportUrl } : {}),
|
|
241
348
|
});
|
|
242
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* updates.json of a phone build, and the same facts in the build's bundle.json: a signer that registers the store build
|
|
353
|
+
* checks the binary reads its URL and keys, and takes `embeddedSequence` as the floor every release must pass.
|
|
354
|
+
*/
|
|
355
|
+
export function writePhoneUpdates(
|
|
356
|
+
path: string,
|
|
357
|
+
outDir: string,
|
|
358
|
+
updates: UpdatesConfig,
|
|
359
|
+
app: { id: string; version: string },
|
|
360
|
+
platform: "ios" | "android",
|
|
361
|
+
nativeApi: string | undefined,
|
|
362
|
+
dev: boolean,
|
|
363
|
+
): void {
|
|
364
|
+
const embeddedSequence = Math.floor(Date.now() / 1000);
|
|
365
|
+
writeFileSync(path, updatesResource(updates, app, platform, nativeApi, dev, embeddedSequence));
|
|
366
|
+
const bundlePath = join(outDir, "bundle.json");
|
|
367
|
+
if (!existsSync(bundlePath)) return;
|
|
368
|
+
const bundle = JSON.parse(readFileSync(bundlePath, "utf8")) as Record<string, unknown>;
|
|
369
|
+
bundle.updates = {
|
|
370
|
+
protocol: 2,
|
|
371
|
+
url: updates.url,
|
|
372
|
+
channel: updates.channel,
|
|
373
|
+
keyIds: updates.publicKeys.map(keyIdOf),
|
|
374
|
+
embeddedSequence,
|
|
375
|
+
...(updates.reportUrl ? { reportUrl: updates.reportUrl } : {}),
|
|
376
|
+
};
|
|
377
|
+
writeFileSync(bundlePath, `${JSON.stringify(bundle, null, 2)}\n`);
|
|
378
|
+
}
|
|
@@ -14,6 +14,7 @@ export const VECTOR_FILES = [
|
|
|
14
14
|
"acl",
|
|
15
15
|
"retained",
|
|
16
16
|
"ed25519",
|
|
17
|
+
"updates",
|
|
17
18
|
] as const;
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -33,6 +34,7 @@ export function vectorCounts(): Record<string, number> {
|
|
|
33
34
|
acl: (v.acl.cases as { checks: unknown[] }[]).reduce((sum, c) => sum + 1 + c.checks.length, 0),
|
|
34
35
|
retained: n(v.retained.cases),
|
|
35
36
|
ed25519: n(v.ed25519.cases),
|
|
37
|
+
updates: n(v.updates.keyIds) + n(v.updates.decisions) + n(v.updates.urls),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -63,7 +63,7 @@ import { androidFeaturesFor, androidPermissionsFor } from "../lib/permissions.ts
|
|
|
63
63
|
import { type AndroidSigning, type BuildContext, SigningError } from "../lib/prepare.ts";
|
|
64
64
|
import type { AndroidManifest, ResolvedConfig } from "../lib/project.ts";
|
|
65
65
|
import { PACKAGE_ROOT } from "../lib/root.ts";
|
|
66
|
-
import {
|
|
66
|
+
import { writePhoneUpdates } from "../lib/updates.ts";
|
|
67
67
|
import { kotlinVectorData } from "../lib/vectors.ts";
|
|
68
68
|
|
|
69
69
|
const SHELL_SRC = join(PACKAGE_ROOT, "native", "android", "src");
|
|
@@ -488,9 +488,14 @@ export async function buildAndroid(ctx: BuildContext): Promise<string> {
|
|
|
488
488
|
const boot = makeNativeBoot(ctx, "android");
|
|
489
489
|
writeFileSync(join(assets, "akan-native", "boot.json"), JSON.stringify(boot));
|
|
490
490
|
if (config.updates)
|
|
491
|
-
|
|
491
|
+
writePhoneUpdates(
|
|
492
492
|
join(assets, "akan-native", "updates.json"),
|
|
493
|
-
|
|
493
|
+
ctx.outDir,
|
|
494
|
+
config.updates,
|
|
495
|
+
config.app,
|
|
496
|
+
"android",
|
|
497
|
+
boot.nativeApi,
|
|
498
|
+
ctx.dev,
|
|
494
499
|
);
|
|
495
500
|
writeFileSync(join(assets, "akan-native", "env.runtime.json"), JSON.stringify(ctx.env, null, 2));
|
|
496
501
|
writeFileSync(
|
|
@@ -498,7 +503,7 @@ export async function buildAndroid(ctx: BuildContext): Promise<string> {
|
|
|
498
503
|
JSON.stringify({
|
|
499
504
|
backgroundColor: config.shell.backgroundColor,
|
|
500
505
|
backgroundColorDark: config.shell.backgroundColorDark,
|
|
501
|
-
devtools: ctx.dev,
|
|
506
|
+
devtools: ctx.dev || config.shell.devtools,
|
|
502
507
|
splash: { autoHide: config.splash.autoHide, timeout: config.splash.timeout },
|
|
503
508
|
|
|
504
509
|
externalSchemes: config.security?.shell?.externalSchemes ?? [],
|
|
@@ -95,7 +95,7 @@ export async function publishAppUpdate(
|
|
|
95
95
|
build: (
|
|
96
96
|
JSON.parse(readFileSync(join(resourcesOf(os, app), "updates.json"), "utf8")) as { embeddedSequence: number }
|
|
97
97
|
).embeddedSequence,
|
|
98
|
-
arch: hostArch(),
|
|
98
|
+
arch: ctx.arch ?? hostArch(),
|
|
99
99
|
files: [{ path: basename(app), sha256: tarSha, size: tar.length }],
|
|
100
100
|
archive: { sha256: tarSha, url: `app/${tarSha}.tar.gz`, size: gz.length, gzSha256: sha256(gz) },
|
|
101
101
|
patches,
|
|
@@ -232,7 +232,8 @@ export function writeDesktopResources(
|
|
|
232
232
|
`the server's native addons would not load on a user's computer:\n - ${addons.problems.join("\n - ")}`,
|
|
233
233
|
);
|
|
234
234
|
cpSync(server.dir, join(resources, "server"), { recursive: true, verbatimSymlinks: true });
|
|
235
|
-
|
|
235
|
+
const { entry, env, restart, watchdog } = server;
|
|
236
|
+
writeFileSync(join(resources, "server.json"), JSON.stringify({ entry, env, restart, watchdog }));
|
|
236
237
|
writeFileSync(join(resources, "server.bunfig.toml"), "");
|
|
237
238
|
}
|
|
238
239
|
if (config.desktop.bin)
|
|
@@ -243,12 +244,13 @@ export function writeDesktopResources(
|
|
|
243
244
|
title: config.app.name,
|
|
244
245
|
backgroundColor: config.shell.backgroundColor,
|
|
245
246
|
backgroundColorDark: config.shell.backgroundColorDark,
|
|
246
|
-
devtools: ctx.dev,
|
|
247
|
+
devtools: ctx.dev || config.shell.devtools,
|
|
247
248
|
quitOnLastWindowClosed: config.desktop.quitOnLastWindowClosed,
|
|
248
249
|
recovery: config.desktop.recovery,
|
|
249
250
|
screenCapture: config.desktop.screenCapture,
|
|
250
251
|
fullscreen: config.desktop.window.fullscreen,
|
|
251
252
|
skipTaskbar: config.desktop.window.skipTaskbar,
|
|
253
|
+
frameless: config.desktop.window.frameless,
|
|
252
254
|
|
|
253
255
|
deepLinks: config.deepLinks.schemes,
|
|
254
256
|
|
|
@@ -52,7 +52,7 @@ import { type BuildContext, SigningError } from "../lib/prepare.ts";
|
|
|
52
52
|
import { privacyApiProblems, privacyManifest } from "../lib/privacy.ts";
|
|
53
53
|
import type { IosManifest } from "../lib/project.ts";
|
|
54
54
|
import { PACKAGE_ROOT } from "../lib/root.ts";
|
|
55
|
-
import {
|
|
55
|
+
import { writePhoneUpdates } from "../lib/updates.ts";
|
|
56
56
|
import { swiftVectorData } from "../lib/vectors.ts";
|
|
57
57
|
|
|
58
58
|
const SHELL_SRC = join(PACKAGE_ROOT, "native", "ios", "Sources");
|
|
@@ -192,9 +192,14 @@ export async function buildIos(ctx: BuildContext): Promise<string> {
|
|
|
192
192
|
const boot = makeNativeBoot(ctx, "ios");
|
|
193
193
|
writeFileSync(join(appPath, "boot.json"), JSON.stringify(boot));
|
|
194
194
|
if (config.updates)
|
|
195
|
-
|
|
195
|
+
writePhoneUpdates(
|
|
196
196
|
join(appPath, "updates.json"),
|
|
197
|
-
|
|
197
|
+
ctx.outDir,
|
|
198
|
+
config.updates,
|
|
199
|
+
config.app,
|
|
200
|
+
"ios",
|
|
201
|
+
boot.nativeApi,
|
|
202
|
+
ctx.dev,
|
|
198
203
|
);
|
|
199
204
|
writeFileSync(join(appPath, "env.runtime.json"), JSON.stringify(ctx.env, null, 2));
|
|
200
205
|
writeFileSync(
|
|
@@ -202,7 +207,7 @@ export async function buildIos(ctx: BuildContext): Promise<string> {
|
|
|
202
207
|
JSON.stringify({
|
|
203
208
|
backgroundColor: config.shell.backgroundColor,
|
|
204
209
|
backgroundColorDark: config.shell.backgroundColorDark,
|
|
205
|
-
devtools: ctx.dev,
|
|
210
|
+
devtools: ctx.dev || config.shell.devtools,
|
|
206
211
|
splash: { autoHide: config.splash.autoHide, timeout: config.splash.timeout },
|
|
207
212
|
|
|
208
213
|
externalSchemes: config.security?.shell?.externalSchemes ?? [],
|
|
@@ -12,6 +12,11 @@ import { windowsVersion } from "./windows.ts";
|
|
|
12
12
|
|
|
13
13
|
/** Microsoft's permanent link to the Evergreen Bootstrapper (MicrosoftEdgeWebview2Setup.exe). */
|
|
14
14
|
export const WEBVIEW2_BOOTSTRAPPER_URL = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
|
|
15
|
+
/** The Evergreen Standalone Installer per CPU (MicrosoftEdgeWebView2RuntimeInstaller<X64|ARM64>.exe). */
|
|
16
|
+
export const WEBVIEW2_STANDALONE_URLS = {
|
|
17
|
+
x64: "https://go.microsoft.com/fwlink/?linkid=2124701",
|
|
18
|
+
arm64: "https://go.microsoft.com/fwlink/?linkid=2099616",
|
|
19
|
+
} as const;
|
|
15
20
|
/** The WebView2 Runtime's EdgeUpdate client id; its `pv` value is the installed version. */
|
|
16
21
|
const WEBVIEW2_CLIENT = "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
|
17
22
|
/** MAX_PATH less its NUL: what a PC without long paths enabled (the Windows default) can open or unpack. */
|
|
@@ -39,6 +44,8 @@ export interface InstallerInput {
|
|
|
39
44
|
/** deepLinks.schemes: the uninstaller removes those the app registered for its own executable. */
|
|
40
45
|
schemes: string[];
|
|
41
46
|
icon?: string;
|
|
47
|
+
/** desktop.publisher: the Publisher Apps & features lists, and the setup program's company name. */
|
|
48
|
+
publisher?: string;
|
|
42
49
|
bootstrapper: string;
|
|
43
50
|
stopScript: string;
|
|
44
51
|
outFile: string;
|
|
@@ -108,7 +115,7 @@ VIAddVersionKey "ProductName" "\${APP_NAME}"
|
|
|
108
115
|
VIAddVersionKey "ProductVersion" "${nsis(input.version)}"
|
|
109
116
|
VIAddVersionKey "FileVersion" "${version}"
|
|
110
117
|
VIAddVersionKey "FileDescription" "\${APP_NAME} Setup"
|
|
111
|
-
|
|
118
|
+
${input.publisher ? `VIAddVersionKey "CompanyName" "${nsis(input.publisher)}"\n` : ""}
|
|
112
119
|
${icon}!define MUI_FINISHPAGE_RUN "$INSTDIR\\\${EXE}"
|
|
113
120
|
!insertmacro MUI_PAGE_INSTFILES
|
|
114
121
|
!insertmacro MUI_PAGE_FINISH
|
|
@@ -399,7 +406,7 @@ ${moveIn(input.entries)}
|
|
|
399
406
|
CreateShortcut "$SMPROGRAMS\\\${APP_NAME}.lnk" "$INSTDIR\\\${EXE}"
|
|
400
407
|
WriteRegStr HKCU "\${UNINSTALL_KEY}" "DisplayName" "\${APP_NAME}"
|
|
401
408
|
WriteRegStr HKCU "\${UNINSTALL_KEY}" "DisplayVersion" "${nsis(input.version)}"
|
|
402
|
-
WriteRegStr HKCU "\${UNINSTALL_KEY}" "DisplayIcon" "$INSTDIR\\\${EXE}"
|
|
409
|
+
${input.publisher ? ` WriteRegStr HKCU "\${UNINSTALL_KEY}" "Publisher" "${nsis(input.publisher)}"\n` : ""} WriteRegStr HKCU "\${UNINSTALL_KEY}" "DisplayIcon" "$INSTDIR\\\${EXE}"
|
|
403
410
|
WriteRegStr HKCU "\${UNINSTALL_KEY}" "InstallLocation" "$INSTDIR"
|
|
404
411
|
WriteRegStr HKCU "\${UNINSTALL_KEY}" "UninstallString" '"$INSTDIR.uninstall.exe"'
|
|
405
412
|
WriteRegStr HKCU "\${UNINSTALL_KEY}" "QuietUninstallString" '"$INSTDIR.uninstall.exe" /S'
|
|
@@ -525,16 +532,22 @@ export function findMakensis(env: Record<string, string | undefined> = process.e
|
|
|
525
532
|
return null;
|
|
526
533
|
}
|
|
527
534
|
|
|
528
|
-
/** The
|
|
529
|
-
async function
|
|
530
|
-
const
|
|
535
|
+
/** The WebView2 setup the installer embeds, downloaded once into the akan-native cache and checked. */
|
|
536
|
+
async function webview2Setup(mode: "bootstrapper" | "standalone", arch: "x64" | "arm64"): Promise<string> {
|
|
537
|
+
const [name, url, minBytes] =
|
|
538
|
+
mode === "standalone"
|
|
539
|
+
? [`MicrosoftEdgeWebView2RuntimeInstaller${arch.toUpperCase()}.exe`, WEBVIEW2_STANDALONE_URLS[arch], 50_000_000]
|
|
540
|
+
: ["MicrosoftEdgeWebview2Setup.exe", WEBVIEW2_BOOTSTRAPPER_URL, 100_000];
|
|
541
|
+
const file = join(akanNativeHome(), "cache", "webview2", name);
|
|
531
542
|
if (!existsSync(file)) {
|
|
532
|
-
log.info(
|
|
533
|
-
|
|
534
|
-
|
|
543
|
+
log.info(
|
|
544
|
+
`downloading the WebView2 ${mode === "standalone" ? `Runtime for ${arch}` : "Evergreen Bootstrapper"} (${url})`,
|
|
545
|
+
);
|
|
546
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
547
|
+
if (!response.ok) throw new CliError(`the WebView2 ${mode} download answered ${response.status}`);
|
|
535
548
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
536
|
-
if (bytes.length <
|
|
537
|
-
throw new CliError(
|
|
549
|
+
if (bytes.length < minBytes || bytes[0] !== 0x4d || bytes[1] !== 0x5a)
|
|
550
|
+
throw new CliError(`the WebView2 ${mode} download is not a Windows program`);
|
|
538
551
|
mkdirSync(join(file, ".."), { recursive: true });
|
|
539
552
|
writeFileSync(file, bytes);
|
|
540
553
|
}
|
|
@@ -592,7 +605,7 @@ export async function buildWindowsInstaller(ctx: BuildContext, folder: string):
|
|
|
592
605
|
log.warn(
|
|
593
606
|
`${deepest} reaches about ${longest} characters under %LOCALAPPDATA%\\Programs while an update unpacks: Windows refuses paths over ${WINDOWS_MAX_PATH} unless long paths are enabled, so the installer or an update fails on such a PC. Shorten app.name or that path.`,
|
|
594
607
|
);
|
|
595
|
-
const bootstrapper = await
|
|
608
|
+
const bootstrapper = await webview2Setup(config.desktop.webview2, ctx.arch ?? hostArch());
|
|
596
609
|
const size = folderSize(folder);
|
|
597
610
|
writeFileSync(
|
|
598
611
|
script,
|
|
@@ -607,6 +620,7 @@ export async function buildWindowsInstaller(ctx: BuildContext, folder: string):
|
|
|
607
620
|
space: { install: size.disk, unpack: size.bytes + statSync(bootstrapper).size },
|
|
608
621
|
schemes: config.deepLinks.schemes,
|
|
609
622
|
...(existsSync(icon) ? { icon } : {}),
|
|
623
|
+
...(config.desktop.publisher ? { publisher: config.desktop.publisher } : {}),
|
|
610
624
|
bootstrapper,
|
|
611
625
|
stopScript,
|
|
612
626
|
outFile,
|
|
@@ -35,6 +35,19 @@ export function windowsVersion(version: string, build: number): string {
|
|
|
35
35
|
return [...parts, Math.max(0, Math.floor(build))].join(".");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function windowsVersionInfo(
|
|
39
|
+
app: { name: string; version: string; build: number },
|
|
40
|
+
publisher?: string,
|
|
41
|
+
): string[] {
|
|
42
|
+
return [
|
|
43
|
+
`--windows-title=${app.name}`,
|
|
44
|
+
`--windows-description=${app.name}`,
|
|
45
|
+
`--windows-version=${windowsVersion(app.version, app.build)}`,
|
|
46
|
+
|
|
47
|
+
`--windows-publisher=${publisher ?? app.name}`,
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
const exePath = (ctx: BuildContext, dir: string) => join(dir, `${ctx.project.config.app.fileName}.exe`);
|
|
39
52
|
|
|
40
53
|
export async function buildWindows(ctx: BuildContext): Promise<string> {
|
|
@@ -57,9 +70,7 @@ export async function buildWindows(ctx: BuildContext): Promise<string> {
|
|
|
57
70
|
const flags = [
|
|
58
71
|
|
|
59
72
|
"--windows-hide-console",
|
|
60
|
-
|
|
61
|
-
`--windows-description=${config.app.name}`,
|
|
62
|
-
`--windows-version=${windowsVersion(config.app.version, config.app.build)}`,
|
|
73
|
+
...windowsVersionInfo(config.app, config.desktop.publisher),
|
|
63
74
|
];
|
|
64
75
|
if (art) {
|
|
65
76
|
const icon = join(outDir, "gen", "app.ico");
|
|
@@ -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
|
+
}
|
|
@@ -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". */
|