@k2b/cloud 0.42.0 → 0.44.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/package.json +13 -5
- package/scripts/app-favicon.ts +7 -6
- package/scripts/build.ts +3 -0
- package/scripts/installable-assets.ts +61 -0
- package/scripts/preload.ts +3 -0
- package/src/_internal/app-access-path.ts +12 -0
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +185 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +10 -5
- package/src/_internal/define-app.ts +159 -45
- package/src/_internal/installable-assets.ts +90 -0
- package/src/_internal/installable-head.ts +22 -0
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/page-responses.ts +34 -3
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +15 -1
- package/src/_internal/registry.ts +44 -1
- package/src/_internal/runtime-context.ts +9 -1
- package/src/_internal/runtime-watcher.ts +8 -0
- package/src/_internal/status-preserving-ssr.ts +12 -1
- package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
- package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
- package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
- package/src/access/AppAccessEditor.tsx +380 -0
- package/src/access/PermissionEditor.browser-harness.tsx +22 -2
- package/src/access/PermissionEditor.tsx +386 -77
- package/src/access/PrincipalAvatar.tsx +63 -0
- package/src/access/PrincipalPicker.tsx +29 -8
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-adapter.ts +51 -0
- package/src/access/app-access-messages.ts +172 -0
- package/src/access/messages.ts +14 -0
- package/src/access/ui.ts +4 -1
- package/src/account/Avatar.tsx +21 -3
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +8 -1
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/capability-execution.ts +5 -2
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/code-capability-routes.ts +1 -0
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +38 -27
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +6 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api-slots.ts +10 -2
- package/src/ai/openai-api.ts +83 -16
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +138 -0
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider-retry.ts +1 -1
- package/src/ai/provider.ts +2 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +11 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas-migrate.ts +1 -0
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +29 -46
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +37 -21
- package/src/ai/turn-failure.ts +24 -2
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +40 -16
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/admin-directory-shares.ts +55 -0
- package/src/api/admin-identity.ts +81 -2
- package/src/api/app-access.ts +85 -0
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +26 -3
- package/src/api/capabilities.ts +127 -15
- package/src/api/capability-streams.ts +2 -5
- package/src/api/identity-child-invocation.ts +145 -0
- package/src/api/index.ts +11 -2
- package/src/api/mcp.ts +180 -5
- package/src/api/me.ts +38 -17
- package/src/api/pwa-phone.ts +87 -48
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/api/widgets.ts +1 -0
- package/src/app-pwa/authority.ts +75 -0
- package/src/app-pwa/contracts.ts +177 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +23 -0
- package/src/app-pwa/protocol.ts +27 -0
- package/src/app-pwa/schema.ts +59 -0
- package/src/app-pwa/service.ts +861 -0
- package/src/app-pwa/transaction.ts +338 -0
- package/src/app-pwa/transport-input.ts +57 -0
- package/src/app-pwa/transport.ts +235 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/app-access-recovery.ts +44 -0
- package/src/browser/app-session.ts +14 -3
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/live-websocket.ts +5 -0
- package/src/browser/pwa-drafts.ts +57 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/resource-picker.tsx +3 -2
- package/src/capabilities/calendar-sources.ts +302 -0
- package/src/capabilities/executions.ts +10 -3
- package/src/capabilities/migrate.ts +4 -0
- package/src/capabilities/nested.ts +43 -0
- package/src/capabilities/server-invoke.ts +202 -0
- package/src/capabilities/server.ts +26 -187
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/account.ts +57 -0
- package/src/cli/admin/agents.ts +66 -0
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +7 -3
- package/src/cli/app-access.ts +60 -0
- package/src/cli/apps.ts +89 -26
- package/src/cli/avatar.ts +64 -0
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/cli/index.ts +1 -1
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +52 -5
- package/src/contracts/app-approval.ts +8 -1
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +24 -5
- package/src/contracts/calendar-source.ts +274 -0
- package/src/contracts/calendar-view.ts +35 -0
- package/src/contracts/capabilities.ts +164 -3
- package/src/contracts/geo-admin.ts +2 -0
- package/src/contracts/geo-route.ts +0 -11
- package/src/contracts/geo.ts +14 -2
- package/src/contracts/index.ts +6 -0
- package/src/contracts/installable.ts +40 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/profile.ts +32 -0
- package/src/contracts/pwa-builds.ts +14 -0
- package/src/contracts/pwa-location.ts +38 -0
- package/src/contracts/pwa.ts +24 -3
- package/src/contracts/registry.ts +8 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +4 -0
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +2 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +7 -6
- package/src/server/middleware/auth.ts +168 -66
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +4 -2
- package/src/server/middleware/workload.ts +3 -3
- package/src/server/oauth-scope.ts +10 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +35 -3
- package/src/services/accounts/avatar.ts +342 -35
- package/src/services/accounts/base-group.ts +4 -5
- package/src/services/accounts/directory-shares.ts +157 -0
- package/src/services/accounts/entities.ts +50 -6
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +64 -12
- package/src/services/accounts/local-groups.ts +1 -1
- package/src/services/accounts/users.ts +23 -15
- package/src/services/app-access-management.ts +519 -0
- package/src/services/app-access.ts +25 -7
- package/src/services/app-approval.ts +15 -4
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/admin.ts +4 -1
- package/src/services/geo/ledger.ts +47 -14
- package/src/services/geo/route-chain.ts +40 -18
- package/src/services/geo/route-estimate.ts +19 -0
- package/src/services/geo/route-plan.ts +0 -2
- package/src/services/geo/route-service.ts +57 -39
- package/src/services/geo/service.ts +51 -25
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/index.ts +2 -1
- package/src/services/identity/invocation-actor.ts +50 -2
- package/src/services/identity/invocation-authority.ts +38 -1
- package/src/services/identity/invocation-token.ts +255 -50
- package/src/services/identity/issue-invocation.ts +9 -1
- package/src/services/identity/models-only-auth.ts +13 -4
- package/src/services/identity/nested-invocation.ts +128 -0
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +24 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/policy.ts +19 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/service-account-credentials.ts +5 -2
- package/src/services/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +91 -47
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +11 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +114 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/AppPwaRuntime.island.tsx +82 -0
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +15 -7
- package/src/ssr/PwaLayout.tsx +73 -8
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/PwaRuntime.island.tsx +85 -6
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/ShellRuntime.island.tsx +5 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-pwa-document.tsx +16 -0
- package/src/ssr/app-pwa-messages.ts +17 -0
- package/src/ssr/index.ts +8 -0
- package/src/ssr/layout-context.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +10 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +2 -0
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/runtime/executor.ts +132 -33
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { type AppSkillDefinition, appSkillManifestHash, registerAppSkills, validateAppSkillCatalog } from "../ai/app-skills";
|
|
2
|
+
import { createOwnedAppAccessRoutes } from "../api/app-access";
|
|
3
|
+
import { createAppEventDeliveryRoutes } from "../api/app-event-delivery";
|
|
4
|
+
import { createResourceAppAccessRoutes } from "../api/app-resource-access";
|
|
2
5
|
import { type AppAccessDeclaration, compileAppAccess } from "../contracts/app-access";
|
|
6
|
+
import { startAppEventDelivery } from "../events/app-event-delivery";
|
|
7
|
+
import { bindAppEvents } from "../events/app-events";
|
|
3
8
|
import { startLiveOutbox } from "../events/live";
|
|
4
9
|
import { registerHelp } from "../services/help";
|
|
5
10
|
import { preloadLayoutHelp } from "../ssr/help";
|
|
6
11
|
import { registerPageSwap } from "../ssr/page-swap";
|
|
7
12
|
import { preloadPanelConsent } from "../ssr/panel-consent";
|
|
13
|
+
import { appAccessManagementPath } from "./app-access-path";
|
|
14
|
+
import { bindResourceAppAccess, RESOURCE_ACCESS_PATH } from "./app-access-resources";
|
|
15
|
+
import { registerApplicationPackage, resolveApplicationPackage } from "./application-package";
|
|
8
16
|
import { invokeCapabilityStream } from "./capability-streams";
|
|
9
17
|
import { withCapabilityRequestTimeout } from "./capability-timeout";
|
|
10
18
|
import { bindProcessApplicationId, clearProcessApplicationId } from "./process-identity";
|
|
@@ -17,7 +25,7 @@ import { bindProcessApplicationId, clearProcessApplicationId } from "./process-i
|
|
|
17
25
|
|
|
18
26
|
import { resolve } from "node:path";
|
|
19
27
|
import { fileURLToPath } from "node:url";
|
|
20
|
-
import type { SsrConfig } from "@k2b/ssr";
|
|
28
|
+
import type { HtmlFn, SsrConfig } from "@k2b/ssr";
|
|
21
29
|
import { createConfig as createSsrConfig } from "@k2b/ssr";
|
|
22
30
|
import { routes } from "@k2b/ssr/hono";
|
|
23
31
|
import type { Sync } from "@k2b/sync";
|
|
@@ -26,6 +34,7 @@ import { generateSpecs } from "hono-openapi";
|
|
|
26
34
|
import { CLOUD_CLI_PLUGIN_ROUTE } from "../cli/plugin";
|
|
27
35
|
import { env } from "../config/env";
|
|
28
36
|
import type {
|
|
37
|
+
AppAdminArea,
|
|
29
38
|
AppAdminNavigationGroup,
|
|
30
39
|
AppAppearance,
|
|
31
40
|
AppCliModules,
|
|
@@ -44,9 +53,11 @@ import {
|
|
|
44
53
|
type CapabilityDefinitions,
|
|
45
54
|
CapabilityOriginSchema,
|
|
46
55
|
} from "../contracts/capabilities";
|
|
56
|
+
import { type AppInstallable, installableContains, validateInstallable } from "../contracts/installable";
|
|
47
57
|
import { type BoundNotificationMap, bindNotificationDefinitions, type NotificationDefinitionMap } from "../contracts/notification-types";
|
|
48
58
|
import { type PlatformPermission, PlatformPermissionSchema } from "../contracts/outgoing-mail";
|
|
49
59
|
import { isPwaPartId, PWA_AUTH_PATH, PWA_CANVAS_COLORS, PWA_MANIFEST_PATH, PWA_SHELL_APP_ID } from "../contracts/pwa";
|
|
60
|
+
import { rememberedPwaAddress } from "../contracts/pwa-location";
|
|
50
61
|
import type { AppRegistryEntry } from "../contracts/registry";
|
|
51
62
|
import type { AppSettingsMap, KindToType } from "../contracts/settings-types";
|
|
52
63
|
import type { Role } from "../contracts/shared";
|
|
@@ -56,6 +67,7 @@ import { requireAppAccess, stopAppAccessConnections } from "../server/middleware
|
|
|
56
67
|
import { type AuthContext, auth } from "../server/middleware/auth";
|
|
57
68
|
import { requireInvocation } from "../server/middleware/invocation";
|
|
58
69
|
import "../events/app-access";
|
|
70
|
+
import { createNestedCapabilityQuery } from "../capabilities/nested";
|
|
59
71
|
import { matchedRouteTemplate, routeTemplate } from "../server/middleware/route-template";
|
|
60
72
|
import { runtime as runtimeMiddleware } from "../server/middleware/runtime";
|
|
61
73
|
import { preloadLayoutAnnouncements, settings as settingsMiddleware } from "../server/middleware/settings";
|
|
@@ -92,6 +104,8 @@ import { FONT_PRELOAD_LINKS } from "./font-preloads";
|
|
|
92
104
|
import { createHeartbeat, createRegistryMembership } from "./heartbeat";
|
|
93
105
|
import { compileHelp } from "./help";
|
|
94
106
|
import { createHttpDrain } from "./http-drain";
|
|
107
|
+
import { createInstallableRoutes } from "./installable-assets";
|
|
108
|
+
import { installableHead, installableThemeScript } from "./installable-head";
|
|
95
109
|
import { createPageResponses } from "./page-responses";
|
|
96
110
|
import { configurePostgresApplicationName } from "./postgres-application-name";
|
|
97
111
|
import { getProcessSync, startProcessSync } from "./process-sync";
|
|
@@ -117,9 +131,18 @@ export type PageOptions = {
|
|
|
117
131
|
lang?: string;
|
|
118
132
|
/** Renders the document as a page of the installable mobile app; set by `PwaLayout`. */
|
|
119
133
|
pwa?: { name: string };
|
|
134
|
+
/** A successful app document may keep its same-document address changes. */
|
|
135
|
+
pwaAddress?: string;
|
|
136
|
+
/** The actual rendering application, including on error pages. */
|
|
137
|
+
pwaOwner?: string;
|
|
120
138
|
};
|
|
121
139
|
|
|
122
|
-
type ResolvedPageOptions = PageOptions & {
|
|
140
|
+
type ResolvedPageOptions = PageOptions & {
|
|
141
|
+
installable?: AppInstallable;
|
|
142
|
+
performanceRoute?: string;
|
|
143
|
+
themePreference?: CloudThemePreference;
|
|
144
|
+
appAccent?: string;
|
|
145
|
+
};
|
|
123
146
|
|
|
124
147
|
// ── Public types ────────────────────────────────────────────────────────────
|
|
125
148
|
|
|
@@ -135,6 +158,8 @@ type ResolvedPageOptions = PageOptions & { performanceRoute?: string; themePrefe
|
|
|
135
158
|
*/
|
|
136
159
|
export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
|
|
137
160
|
id: AppId;
|
|
161
|
+
/** Shared object-store package. Defaults to the app ID. */
|
|
162
|
+
package?: string;
|
|
138
163
|
access?: AppAccessDeclaration;
|
|
139
164
|
name: string;
|
|
140
165
|
icon: string;
|
|
@@ -151,7 +176,7 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
151
176
|
adminHref?: string;
|
|
152
177
|
/** Multi-link admin navigation contributed by this app. */
|
|
153
178
|
adminNav?: ReadonlyArray<{
|
|
154
|
-
section?:
|
|
179
|
+
section?: AppAdminArea;
|
|
155
180
|
id?: string;
|
|
156
181
|
label: string;
|
|
157
182
|
links: ReadonlyArray<{ label: string; href: string; icon: string }>;
|
|
@@ -167,7 +192,7 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
167
192
|
* Same-origin path of a `GET` route this app serves below one of its
|
|
168
193
|
* `routes`. After the page loads, the shell reads it with the person's
|
|
169
194
|
* session and shows the returned `AppNavBadge` count on the app's icon in
|
|
170
|
-
* the rail and the app grid. Protect it like any other route.
|
|
195
|
+
* the rail, the phone bar, and the app grid. Protect it like any other route.
|
|
171
196
|
*/
|
|
172
197
|
badge?: string;
|
|
173
198
|
};
|
|
@@ -197,6 +222,8 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
197
222
|
legalLinks?: ReadonlyArray<{ label: string; href: string; icon?: string }>;
|
|
198
223
|
/** Static destinations for the global search UI; no capabilities are created. */
|
|
199
224
|
searchLinks?: readonly AppSearchLink[];
|
|
225
|
+
/** One standalone App-PWA at a canonical, declared own /app/<id>/<surface>/ path. */
|
|
226
|
+
installable?: AppInstallable;
|
|
200
227
|
/**
|
|
201
228
|
* Preview: pages of this app in the installable mobile app. Declaring it adds
|
|
202
229
|
* `/pwa/<id>` to the gateway routes and lists the app in the mobile app,
|
|
@@ -206,6 +233,17 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
206
233
|
pwa?: {
|
|
207
234
|
/** Coarse visibility in the mobile app, like `nav.requiresRoles`. Routes and services still authorize. */
|
|
208
235
|
requiresRoles?: Role[];
|
|
236
|
+
/**
|
|
237
|
+
* Same-origin path of a `GET` route this app serves that returns an `AppNavBadge`: the count beside the part in
|
|
238
|
+
* the app's sidebar, such as unread messages. Like `nav.badge`, it must be one of the app's own paths.
|
|
239
|
+
*/
|
|
240
|
+
badge?: string;
|
|
241
|
+
/**
|
|
242
|
+
* Same-origin path of a `GET` route this app serves that returns a `PwaPartSummary`: one line under the part on
|
|
243
|
+
* Start, such as "5 tasks for you", in the reader's locale, and its count beside the part in the sidebar when the
|
|
244
|
+
* part has no `badge`. It may be the badge's route. It must be one of the app's own paths.
|
|
245
|
+
*/
|
|
246
|
+
summary?: string;
|
|
209
247
|
};
|
|
210
248
|
/**
|
|
211
249
|
* Dashboard widget endpoints this app exposes. Each entry references an
|
|
@@ -226,8 +264,8 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
226
264
|
*
|
|
227
265
|
* Apps with non-standard URLs (core's `/auth`, `/me`, `/legal/*`, `/impressum`;
|
|
228
266
|
* oauth's `/oauth`, `/.well-known/...`) list whatever
|
|
229
|
-
* top-level paths they own. The gateway
|
|
230
|
-
*
|
|
267
|
+
* top-level paths they own. The gateway builds a prefix trie and enforces
|
|
268
|
+
* /app/<id> ownership and the reserved /pwa paths.
|
|
231
269
|
*/
|
|
232
270
|
routes: readonly string[];
|
|
233
271
|
/** Platform services requested by this application, visible to administrators. */
|
|
@@ -378,7 +416,21 @@ const validateAppPwaPart = (opts: Pick<AppOptions, "id" | "routes" | "pwa">): Ap
|
|
|
378
416
|
if (!isPwaPartId(opts.id)) {
|
|
379
417
|
throw new Error(`App "${opts.id}" cannot declare pwa; a part needs a plain id other than pwa, settings and offline`);
|
|
380
418
|
}
|
|
381
|
-
|
|
419
|
+
const { badge, summary } = opts.pwa;
|
|
420
|
+
for (const [field, path] of [
|
|
421
|
+
["badge", badge],
|
|
422
|
+
["summary", summary],
|
|
423
|
+
] as const) {
|
|
424
|
+
if (path !== undefined && !isOwnAppPath(path, [...opts.routes, `/pwa/${opts.id}`])) {
|
|
425
|
+
throw new Error(`App "${opts.id}" declares pwa.${field} "${path}"; it must be a same-origin path below one of its routes`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
href: `/pwa/${opts.id}`,
|
|
430
|
+
requiresRoles: opts.pwa.requiresRoles ? [...opts.pwa.requiresRoles] : undefined,
|
|
431
|
+
...(badge ? { badge } : {}),
|
|
432
|
+
...(summary ? { summary } : {}),
|
|
433
|
+
};
|
|
382
434
|
};
|
|
383
435
|
|
|
384
436
|
/** The badge route is read by the browser through the gateway, so it must be one of this app's own paths. */
|
|
@@ -430,6 +482,11 @@ export const defineApp = <
|
|
|
430
482
|
const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
|
|
431
483
|
const cliModules = validateAppCliModules(opts.id, opts.cli);
|
|
432
484
|
const pwaPart = validateAppPwaPart(opts);
|
|
485
|
+
const packageName = resolveApplicationPackage(opts.id, opts.package);
|
|
486
|
+
const installable = opts.installable === undefined ? undefined : validateInstallable(opts.id, opts.routes, opts.installable);
|
|
487
|
+
if (installable === null)
|
|
488
|
+
throw new Error(`App "${opts.id}" declares invalid installable; use a canonical own /app/<id>/<surface>/ path covered by routes`);
|
|
489
|
+
const requestInstallable = (path: string) => (installable && installableContains(installable.path, path) ? installable : undefined);
|
|
433
490
|
validateAppNavBadge(opts);
|
|
434
491
|
const panel = validateAppPanel(opts);
|
|
435
492
|
|
|
@@ -444,7 +501,11 @@ export const defineApp = <
|
|
|
444
501
|
|
|
445
502
|
// ── 1. SSR config ─────────────────────────────────────────────────────
|
|
446
503
|
const rootDir = opts.appRoot ?? process.cwd();
|
|
447
|
-
const {
|
|
504
|
+
const {
|
|
505
|
+
config,
|
|
506
|
+
plugin,
|
|
507
|
+
html: renderHtml,
|
|
508
|
+
} = createSsrConfig<ResolvedPageOptions>({
|
|
448
509
|
dev: isDevelopment,
|
|
449
510
|
verbose: true,
|
|
450
511
|
rootDir,
|
|
@@ -453,7 +514,19 @@ export const defineApp = <
|
|
|
453
514
|
// Every island instance already mounts inside its own error boundary; this
|
|
454
515
|
// only gives the fallback Cloud's look and the reader's language.
|
|
455
516
|
errorFallback: fileURLToPath(new URL("../ssr/IslandError.tsx", import.meta.url)),
|
|
456
|
-
template: ({
|
|
517
|
+
template: ({
|
|
518
|
+
body,
|
|
519
|
+
scripts,
|
|
520
|
+
title,
|
|
521
|
+
description,
|
|
522
|
+
theme,
|
|
523
|
+
themePreference,
|
|
524
|
+
lang,
|
|
525
|
+
performanceRoute,
|
|
526
|
+
pwa,
|
|
527
|
+
appAccent,
|
|
528
|
+
installable: pageInstallable,
|
|
529
|
+
}) => {
|
|
457
530
|
// A URL changes only when what it names changes: the app's own files by their content, Core's shared sheets by
|
|
458
531
|
// the release of the Core build in the registry, so every app links them under one URL per Core build.
|
|
459
532
|
const sharedVersion = isDevelopment ? String(developmentStamp) : sharedStylesheetVersion(registeredCoreRelease());
|
|
@@ -471,33 +544,35 @@ export const defineApp = <
|
|
|
471
544
|
// status bar. No status-bar style: `black-translucent` lays a blur band
|
|
472
545
|
// over the top edge on iOS 26.
|
|
473
546
|
const canvas = resolved ? PWA_CANVAS_COLORS[resolved] : undefined;
|
|
474
|
-
const head =
|
|
475
|
-
?
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
547
|
+
const head = pageInstallable
|
|
548
|
+
? installableHead(pageInstallable, resolved)
|
|
549
|
+
: pwa
|
|
550
|
+
? {
|
|
551
|
+
html: canvas ? ` style="background-color:${canvas}"` : "",
|
|
552
|
+
viewport: "width=device-width, initial-scale=1, viewport-fit=cover",
|
|
553
|
+
app: `${
|
|
554
|
+
canvas
|
|
555
|
+
? `<meta name="theme-color" content="${canvas}">`
|
|
556
|
+
: `<meta name="theme-color" media="(prefers-color-scheme: light)" content="${PWA_CANVAS_COLORS.light}">
|
|
482
557
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="${PWA_CANVAS_COLORS.dark}">
|
|
483
558
|
<style data-cloud-canvas>html{background-color:${PWA_CANVAS_COLORS.light}}@media (prefers-color-scheme: dark){html{background-color:${PWA_CANVAS_COLORS.dark}}}</style>`
|
|
484
|
-
|
|
559
|
+
}
|
|
485
560
|
<link rel="manifest" href="${PWA_MANIFEST_PATH}">
|
|
486
561
|
<link rel="apple-touch-icon" href="/branding/apple-touch-icon.png">
|
|
487
562
|
<meta name="apple-mobile-web-app-title" content="${escapeHtml(pwa.name)}">`,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
563
|
+
// The web cross-fades between pages; an app page swaps at once. The browser ignores taps while a
|
|
564
|
+
// cross-document transition runs, so a tab tap right after the last one would be lost. After
|
|
565
|
+
// global.css, whose `navigation: auto` it overrides.
|
|
566
|
+
styles: `
|
|
492
567
|
<style data-cloud-app-navigation>@view-transition{navigation:none}</style>`,
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
568
|
+
}
|
|
569
|
+
: {
|
|
570
|
+
html: "",
|
|
571
|
+
viewport: "width=device-width, initial-scale=1.0",
|
|
572
|
+
app: `<meta name="theme-color" content="#09090b">
|
|
498
573
|
<meta name="mobile-web-app-capable" content="yes">`,
|
|
499
|
-
|
|
500
|
-
|
|
574
|
+
styles: "",
|
|
575
|
+
};
|
|
501
576
|
// The inline layer statement fixes the cascade order before any
|
|
502
577
|
// stylesheet declares a layer. Tailwind's `properties` layer resets
|
|
503
578
|
// `--tw-*` in browsers without `@property` and must stay lowest.
|
|
@@ -516,7 +591,7 @@ export const defineApp = <
|
|
|
516
591
|
<link rel="stylesheet" href="${versionedPublicHref("/public/tabler-icons.css", sharedVersion)}">
|
|
517
592
|
<link rel="stylesheet" href="${versionedPublicHref(appStylesheet, appVersion)}">
|
|
518
593
|
<link rel="stylesheet" href="${versionedPublicHref("/public/global.css", sharedVersion)}">${head.styles}
|
|
519
|
-
<script>${themeBootstrapScript}</script
|
|
594
|
+
<script>${themeBootstrapScript}</script>${pageInstallable ? `<script>${installableThemeScript(pageInstallable)}</script>` : ""}
|
|
520
595
|
</head>
|
|
521
596
|
<body class="k2b-ui${accentStyle ? " app-accent-scope" : ""}"${accentStyle ? ` style="${escapeHtml(accentStyle)}"` : ""} data-k2b-app-workspace-controller="global">
|
|
522
597
|
${body}
|
|
@@ -527,6 +602,13 @@ export const defineApp = <
|
|
|
527
602
|
},
|
|
528
603
|
});
|
|
529
604
|
|
|
605
|
+
const html: HtmlFn<ResolvedPageOptions> = async (render, page) => {
|
|
606
|
+
if (!page?.installable) return renderHtml(render, page);
|
|
607
|
+
// Load JSX only after the standard preload/build has installed the SSR plugin.
|
|
608
|
+
const { appPwaDocument } = await import("../ssr/app-pwa-document");
|
|
609
|
+
return renderHtml(appPwaDocument(render, page.installable, normalizeLocale(page.lang)), page);
|
|
610
|
+
};
|
|
611
|
+
|
|
530
612
|
// Pass PageOptions explicitly so c.get("page") in apps' SSR handlers is
|
|
531
613
|
// typed as Partial<PageOptions> (with title/description/theme), not the
|
|
532
614
|
// bare `object` fallback the constraint would otherwise produce.
|
|
@@ -539,7 +621,8 @@ export const defineApp = <
|
|
|
539
621
|
// cannot override.
|
|
540
622
|
const ssr = Object.assign(
|
|
541
623
|
createStatusPreservingSsrHandler<ResolvedPageOptions>(html, async (c) => {
|
|
542
|
-
|
|
624
|
+
c.get("page").installable = requestInstallable(c.req.path);
|
|
625
|
+
if (opts.pageSwap && !c.get("page").installable)
|
|
543
626
|
registerPageSwap(c, isDevelopment ? String(developmentStamp) : `${buildMetadata.version}:${buildMetadata.release}`);
|
|
544
627
|
c.get("page").lang = getLocale(c);
|
|
545
628
|
c.get("page").themePreference = readThemePreferenceFromCookieHeader(c.req.header("Cookie"));
|
|
@@ -548,7 +631,14 @@ export const defineApp = <
|
|
|
548
631
|
user && c.get("settings")?.observability?.web_vitals?.enabled === true ? (matchedRouteTemplate(c) ?? undefined) : undefined;
|
|
549
632
|
// Pages of the mobile app render in PwaLayout, which shows no announcements, Help, or rail; reading them would
|
|
550
633
|
// only delay every tab switch. Core's pages below /pwa/_auth, such as its not-found page, keep the web layout.
|
|
551
|
-
if (isAppPagePath(c.req.path))
|
|
634
|
+
if (isAppPagePath(c.req.path)) {
|
|
635
|
+
c.get("page").pwaOwner = opts.id;
|
|
636
|
+
const url = new URL(c.req.url);
|
|
637
|
+
const address = rememberedPwaAddress(url.pathname + url.search);
|
|
638
|
+
if (user && address && c.newResponse(null).status < 400) c.get("page").pwaAddress = address;
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (c.get("page").installable) return;
|
|
552
642
|
await Promise.all([
|
|
553
643
|
preloadLayoutAnnouncements(c),
|
|
554
644
|
preloadLayoutHelp(c),
|
|
@@ -556,11 +646,12 @@ export const defineApp = <
|
|
|
556
646
|
user ? readRailSnapshot(user).then((snapshot) => c.set("railPreferences", snapshot)) : undefined,
|
|
557
647
|
]);
|
|
558
648
|
}),
|
|
559
|
-
createPageResponses(html),
|
|
649
|
+
createPageResponses(html, opts.id, (c) => ({ installable: requestInstallable(c.req.path) })),
|
|
560
650
|
);
|
|
561
651
|
|
|
562
652
|
// ── 2. Meta ───────────────────────────────────────────────────────────
|
|
563
653
|
const baseMeta: AppMeta = {
|
|
654
|
+
package: packageName,
|
|
564
655
|
id: opts.id,
|
|
565
656
|
platformPermissions: opts.platformPermissions
|
|
566
657
|
? opts.platformPermissions.map((permission) => PlatformPermissionSchema.parse(permission))
|
|
@@ -583,6 +674,7 @@ export const defineApp = <
|
|
|
583
674
|
legalLinks: opts.legalLinks ? [...opts.legalLinks] : undefined,
|
|
584
675
|
searchLinks: opts.searchLinks?.map((link) => ({ ...link, keywords: link.keywords ? [...link.keywords] : undefined })),
|
|
585
676
|
pwa: pwaPart,
|
|
677
|
+
installable: installable ?? undefined,
|
|
586
678
|
panel,
|
|
587
679
|
widgets: compileWidgetDeclarations(opts.id, opts.widgets),
|
|
588
680
|
settingKeys: opts.settings ? Object.keys(opts.settings) : undefined,
|
|
@@ -629,7 +721,8 @@ export const defineApp = <
|
|
|
629
721
|
}
|
|
630
722
|
})());
|
|
631
723
|
try {
|
|
632
|
-
|
|
724
|
+
await registerApplicationPackage(meta.id, packageName);
|
|
725
|
+
bindProcessApplicationId(meta.id, meta.platformPermissions, packageName);
|
|
633
726
|
cleanup.unshift(clearProcessApplicationId);
|
|
634
727
|
const startedAt = Date.now();
|
|
635
728
|
|
|
@@ -637,7 +730,8 @@ export const defineApp = <
|
|
|
637
730
|
// derive the spec from. The mount block lower down uses the same
|
|
638
731
|
// flag so the registry never points at a URL that 404s.
|
|
639
732
|
const advertiseOpenapi = !!(opts.openapi && startOpts.openapi);
|
|
640
|
-
const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities) : undefined;
|
|
733
|
+
const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities, meta.routes) : undefined;
|
|
734
|
+
if (startOpts.capabilities) bindAppEvents(meta.id, startOpts.capabilities, compiledCapabilities);
|
|
641
735
|
const compiledHelp = startOpts.help
|
|
642
736
|
? compileHelp({
|
|
643
737
|
appId: meta.id,
|
|
@@ -648,6 +742,7 @@ export const defineApp = <
|
|
|
648
742
|
|
|
649
743
|
// Registry entry
|
|
650
744
|
const entry: AppRegistryEntry = {
|
|
745
|
+
package: packageName,
|
|
651
746
|
id: meta.id,
|
|
652
747
|
access: meta.access,
|
|
653
748
|
name: meta.name,
|
|
@@ -688,8 +783,14 @@ export const defineApp = <
|
|
|
688
783
|
legalLinks: meta.legalLinks ? meta.legalLinks.map((l) => ({ ...l })) : undefined,
|
|
689
784
|
searchLinks: meta.searchLinks,
|
|
690
785
|
pwa: meta.pwa
|
|
691
|
-
? {
|
|
786
|
+
? {
|
|
787
|
+
href: meta.pwa.href,
|
|
788
|
+
requiresRoles: meta.pwa.requiresRoles ? [...meta.pwa.requiresRoles] : undefined,
|
|
789
|
+
...(meta.pwa.badge ? { badge: meta.pwa.badge } : {}),
|
|
790
|
+
...(meta.pwa.summary ? { summary: meta.pwa.summary } : {}),
|
|
791
|
+
}
|
|
692
792
|
: undefined,
|
|
793
|
+
installable: meta.installable,
|
|
693
794
|
panel: meta.panel
|
|
694
795
|
? { ...meta.panel, requiresRoles: meta.panel.requiresRoles ? [...meta.panel.requiresRoles] : undefined }
|
|
695
796
|
: undefined,
|
|
@@ -809,6 +910,11 @@ export const defineApp = <
|
|
|
809
910
|
})
|
|
810
911
|
.route("/_internal/sync", createSyncOpsRoutes(getProcessSync));
|
|
811
912
|
|
|
913
|
+
if (installable) server.route("/", createInstallableRoutes(meta.id, installable));
|
|
914
|
+
|
|
915
|
+
if (compiledCapabilities?.subscribes.size)
|
|
916
|
+
server.route("/api/_internal/events/v1", createAppEventDeliveryRoutes(compiledCapabilities));
|
|
917
|
+
|
|
812
918
|
if (compiledCapabilities) {
|
|
813
919
|
const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") =>
|
|
814
920
|
withCapabilityRequestTimeout(c, kind, async () => {
|
|
@@ -846,6 +952,7 @@ export const defineApp = <
|
|
|
846
952
|
input: body.input,
|
|
847
953
|
expectedSchemaHash: c.req.header("x-cloud-capability-schema-hash") ?? null,
|
|
848
954
|
context: {
|
|
955
|
+
query: createNestedCapabilityQuery(c.req.raw),
|
|
849
956
|
authority: auth.getAuthority(c),
|
|
850
957
|
actor,
|
|
851
958
|
accessSubject: c.get("accessSubject"),
|
|
@@ -898,6 +1005,7 @@ export const defineApp = <
|
|
|
898
1005
|
for (const kind of ["queries", "actions"] as const) {
|
|
899
1006
|
server.post(
|
|
900
1007
|
`/api/_internal/capabilities/v1/streams/${kind}/:capabilityId/:verb`,
|
|
1008
|
+
kind === "queries" ? auth.readOnly : auth.requireOAuthScope("write", "admin"),
|
|
901
1009
|
capabilityAuth(kind),
|
|
902
1010
|
auth.requireOAuthScope(kind === "queries" ? "read" : "write", "admin"),
|
|
903
1011
|
async (c) => {
|
|
@@ -924,14 +1032,19 @@ export const defineApp = <
|
|
|
924
1032
|
}
|
|
925
1033
|
const capabilityReadScope = auth.requireOAuthScope("read", "admin");
|
|
926
1034
|
const capabilityWriteScope = auth.requireOAuthScope("write", "admin");
|
|
927
|
-
server.post(
|
|
928
|
-
|
|
1035
|
+
server.post(
|
|
1036
|
+
"/api/_internal/capabilities/v1/queries/:capabilityId",
|
|
1037
|
+
auth.readOnly,
|
|
1038
|
+
capabilityAuth("queries"),
|
|
1039
|
+
capabilityReadScope,
|
|
1040
|
+
(c) => invoke(c, "query"),
|
|
929
1041
|
);
|
|
930
1042
|
server.post("/api/_internal/capabilities/v1/actions/:capabilityId", capabilityAuth("actions"), capabilityWriteScope, (c) =>
|
|
931
1043
|
invoke(c, "action"),
|
|
932
1044
|
);
|
|
933
1045
|
server.post(
|
|
934
1046
|
"/api/_internal/capabilities/v1/actions/:capabilityId/review",
|
|
1047
|
+
auth.readOnly,
|
|
935
1048
|
capabilityAuth("actions", true),
|
|
936
1049
|
capabilityReadScope,
|
|
937
1050
|
(c) => invoke(c, "review"),
|
|
@@ -964,16 +1077,15 @@ export const defineApp = <
|
|
|
964
1077
|
);
|
|
965
1078
|
}
|
|
966
1079
|
|
|
1080
|
+
cleanup.push(bindResourceAppAccess(meta.id, opts.access));
|
|
1081
|
+
server.route(RESOURCE_ACCESS_PATH, createResourceAppAccessRoutes(meta));
|
|
967
1082
|
cleanup.push(() => stopAppAccessConnections());
|
|
1083
|
+
if (meta.access?.kind === "grants") {
|
|
1084
|
+
server.route(appAccessManagementPath(meta), createOwnedAppAccessRoutes(meta, opts.access?.canManage));
|
|
1085
|
+
}
|
|
968
1086
|
server.use(
|
|
969
1087
|
"*",
|
|
970
|
-
requireAppAccess(meta, (c,
|
|
971
|
-
ssr.error(c, 403, {
|
|
972
|
-
title: "No access",
|
|
973
|
-
description: message,
|
|
974
|
-
layout: "minimal",
|
|
975
|
-
}),
|
|
976
|
-
),
|
|
1088
|
+
requireAppAccess(meta, (c) => ssr.appAccessDenied(c, meta)),
|
|
977
1089
|
);
|
|
978
1090
|
|
|
979
1091
|
if (opts.cli && cliModules.length > 0) server.route(CLOUD_CLI_PLUGIN_ROUTE, createCliPluginRoutes(meta.id, opts.cli));
|
|
@@ -1037,6 +1149,8 @@ export const defineApp = <
|
|
|
1037
1149
|
const startOutbox = async () => {
|
|
1038
1150
|
const stopLiveOutbox = await startLiveOutbox(meta.id);
|
|
1039
1151
|
if (stopLiveOutbox) cleanup.push(stopLiveOutbox);
|
|
1152
|
+
const stopAppEvents = await startAppEventDelivery(meta.id);
|
|
1153
|
+
if (stopAppEvents) cleanup.push(stopAppEvents);
|
|
1040
1154
|
};
|
|
1041
1155
|
// An application fails before any setup when Core has not created the platform outbox yet.
|
|
1042
1156
|
// Core creates it in its own setup, so it starts publishing after that.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import type { AppInstallable } from "../contracts/installable";
|
|
4
|
+
import { escapeHtml } from "../shared/markdown/shared";
|
|
5
|
+
|
|
6
|
+
export const INSTALLABLE_ICON_VARIANTS = {
|
|
7
|
+
"icon-192": { size: 192, box: 0.84 },
|
|
8
|
+
"icon-512": { size: 512, box: 0.84 },
|
|
9
|
+
// The square fits inside the maskable safe circle of radius 40%.
|
|
10
|
+
"icon-maskable-512": { size: 512, box: 0.56 },
|
|
11
|
+
"apple-touch-icon": { size: 180, box: 0.7 },
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const installableManifest = (app: AppInstallable, theme: "light" | "dark") => ({
|
|
15
|
+
id: app.path,
|
|
16
|
+
start_url: app.path,
|
|
17
|
+
scope: app.path,
|
|
18
|
+
name: app.name,
|
|
19
|
+
short_name: app.shortName,
|
|
20
|
+
display: app.display,
|
|
21
|
+
theme_color: app.themeColor[theme],
|
|
22
|
+
background_color: app.themeColor[theme],
|
|
23
|
+
icons: [
|
|
24
|
+
{ src: `${app.path}icon-192-${theme}.png`, sizes: "192x192", type: "image/png", purpose: "any" },
|
|
25
|
+
{ src: `${app.path}icon-512-${theme}.png`, sizes: "512x512", type: "image/png", purpose: "any" },
|
|
26
|
+
{ src: `${app.path}icon-maskable-512-${theme}.png`, sizes: "512x512", type: "image/png", purpose: "maskable" },
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/** One public bilingual document. It never includes a request, account, or application payload. */
|
|
31
|
+
export const installableOfflineHtml = (app: AppInstallable): string => `<!doctype html>
|
|
32
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
33
|
+
<title>${escapeHtml(app.name)} · Offline</title><meta name="referrer" content="no-referrer">
|
|
34
|
+
<style>:root{color-scheme:light dark;font-family:system-ui;background:#fafafa;color:#111}body{margin:0;padding: max(24px,env(safe-area-inset-top)) max(24px,env(safe-area-inset-right)) max(24px,env(safe-area-inset-bottom)) max(24px,env(safe-area-inset-left))}main{max-width:36rem;margin:10vh auto}button{font:inherit;min-height:44px;padding:8px 20px;cursor:pointer}@media(prefers-color-scheme:dark){:root{background:#090d12;color:#fff}}</style>
|
|
35
|
+
</head><body><main><h1>${escapeHtml(app.name)}</h1><p>You're offline. Connect to the internet and try again.</p><p lang="de">Du bist offline. Verbinde dich mit dem Internet und versuche es erneut.</p><button type="button">Try again / Erneut versuchen</button></main><script>document.querySelector('button').addEventListener('click',()=>location.reload())</script></body></html>`;
|
|
36
|
+
|
|
37
|
+
/** No cache storage, install/activate shortcuts, client messages, or private response retention. */
|
|
38
|
+
export const installableWorker = (app: AppInstallable): string => `"use strict";
|
|
39
|
+
const scope = ${JSON.stringify(app.path)};
|
|
40
|
+
const offline = ${JSON.stringify(installableOfflineHtml(app))};
|
|
41
|
+
self.addEventListener("fetch", event => {
|
|
42
|
+
const request = event.request;
|
|
43
|
+
const url = new URL(request.url);
|
|
44
|
+
if (request.method !== "GET" || request.mode !== "navigate" || url.origin !== self.location.origin || !url.pathname.startsWith(scope)) return;
|
|
45
|
+
event.respondWith(fetch(request).catch(() => new Response(offline, {
|
|
46
|
+
status: 503,
|
|
47
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" }
|
|
48
|
+
})));
|
|
49
|
+
});
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
/** Framework mounts before app access/auth and never delegates a missing generated file to app code. */
|
|
53
|
+
export const createInstallableRoutes = (appId: string, app: AppInstallable, publicDir = resolve(process.cwd(), "public")) => {
|
|
54
|
+
const router = new Hono();
|
|
55
|
+
const headers = { "Cache-Control": "no-cache", "X-Content-Type-Options": "nosniff" };
|
|
56
|
+
router.get(app.path.slice(0, -1), (c) => {
|
|
57
|
+
const url = new URL(c.req.url);
|
|
58
|
+
return c.redirect(`${app.path}${url.search}`, 308);
|
|
59
|
+
});
|
|
60
|
+
router.all(`${app.path}manifest.webmanifest`, (c) => {
|
|
61
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.text("Method Not Allowed", 405, { Allow: "GET, HEAD" });
|
|
62
|
+
return new Response(
|
|
63
|
+
c.req.method === "HEAD" ? null : JSON.stringify(installableManifest(app, c.req.query("theme") === "dark" ? "dark" : "light")),
|
|
64
|
+
{
|
|
65
|
+
headers: { ...headers, "Content-Type": "application/manifest+json" },
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
for (const [name, content, type] of [
|
|
70
|
+
["sw.js", installableWorker(app), "text/javascript; charset=utf-8"],
|
|
71
|
+
["offline.html", installableOfflineHtml(app), "text/html; charset=utf-8"],
|
|
72
|
+
] as const) {
|
|
73
|
+
router.all(`${app.path}${name}`, (c) => {
|
|
74
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.text("Method Not Allowed", 405, { Allow: "GET, HEAD" });
|
|
75
|
+
return new Response(c.req.method === "HEAD" ? null : content, { headers: { ...headers, "Content-Type": type } });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const theme of ["light", "dark"] as const) {
|
|
79
|
+
for (const name of Object.keys(INSTALLABLE_ICON_VARIANTS)) {
|
|
80
|
+
const filename = `${name}-${theme}.png`;
|
|
81
|
+
router.all(`${app.path}${filename}`, async (c) => {
|
|
82
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.text("Method Not Allowed", 405, { Allow: "GET, HEAD" });
|
|
83
|
+
const file = Bun.file(resolve(publicDir, appId, "installable", filename));
|
|
84
|
+
if (!(await file.exists())) return c.notFound();
|
|
85
|
+
return new Response(c.req.method === "HEAD" ? null : file, { headers: { ...headers, "Content-Type": "image/png" } });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return router;
|
|
90
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppInstallable } from "../contracts/installable";
|
|
2
|
+
import { escapeHtml } from "../shared/markdown/shared";
|
|
3
|
+
|
|
4
|
+
export const installableHead = (app: AppInstallable, resolved: "light" | "dark" | undefined) => {
|
|
5
|
+
const theme = resolved ?? "light";
|
|
6
|
+
return {
|
|
7
|
+
html: "",
|
|
8
|
+
viewport: "width=device-width, initial-scale=1, viewport-fit=cover",
|
|
9
|
+
app: `<meta name="theme-color" data-app-pwa-theme content="${app.themeColor[theme]}">
|
|
10
|
+
<link rel="manifest" data-app-pwa-manifest href="${app.path}manifest.webmanifest?theme=${theme}">
|
|
11
|
+
<link rel="apple-touch-icon" data-app-pwa-apple href="${app.path}apple-touch-icon-${theme}.png">
|
|
12
|
+
<meta name="apple-mobile-web-app-title" content="${escapeHtml(app.shortName)}">
|
|
13
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
14
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
15
|
+
<style data-app-pwa-canvas>html{background:${app.themeColor.light}}html.dark{background:${app.themeColor.dark}}body{box-sizing:border-box;min-height:100dvh;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}[data-app-pwa-runtime]:not(:empty){max-width:40rem;margin:1rem auto}</style>`,
|
|
16
|
+
styles: "<style data-app-pwa-navigation>@view-transition{navigation:none}</style>",
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Runs after the shared theme bootstrap, before first paint or island downloads. All interpolations are validated paths/hex colors. */
|
|
21
|
+
export const installableThemeScript = (app: AppInstallable): string =>
|
|
22
|
+
`!function(){var t=document.documentElement.classList.contains('dark')?'dark':'light';document.querySelector('meta[data-app-pwa-theme]').content=${JSON.stringify(app.themeColor)}[t];document.querySelector('link[data-app-pwa-manifest]').href=${JSON.stringify(`${app.path}manifest.webmanifest?theme=`)}+t;document.querySelector('link[data-app-pwa-apple]').href=${JSON.stringify(`${app.path}apple-touch-icon-`)}+t+'.png'}();`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getProcessApplicationPackage } from "./process-identity";
|
|
2
|
+
|
|
3
|
+
/** The single application-to-area mapping. No caller-supplied application or area ID. */
|
|
4
|
+
export const objectStoreArea = (visibility: "private" | "public" = "private"): string => {
|
|
5
|
+
const packageName = getProcessApplicationPackage();
|
|
6
|
+
if (!packageName) throw new Error("Start the application before using the object store.");
|
|
7
|
+
if (!/^[a-z][a-z0-9-]{0,79}$/.test(packageName)) throw new Error("Invalid object-store application package.");
|
|
8
|
+
return `${visibility}/${packageName}/`;
|
|
9
|
+
};
|