chore: consolidate platform split checks
This commit is contained in:
@@ -1,19 +1,23 @@
|
||||
import { Activity, CalendarDays, FileText, Folder, Form, LayoutDashboard, Mail, Mails, Shield, Users, type LucideIcon } from "lucide-react";
|
||||
import installedWebModules from "virtual:govoplan-installed-modules";
|
||||
import type { AuthInfo, PlatformModuleInfo, PlatformNavItem, PlatformWebModule } from "../types";
|
||||
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, PlatformModuleInfo, PlatformNavItem, PlatformWebModule } from "../types";
|
||||
import {
|
||||
hasUiCapability as hasUiCapabilityForModules,
|
||||
moduleInstalled as moduleInstalledForModules,
|
||||
moduleIntegrationEnabled as moduleIntegrationEnabledForModules,
|
||||
routeContributionsForModules as routeContributionsForModuleList,
|
||||
uiCapabilities as uiCapabilitiesForModules,
|
||||
uiCapability as uiCapabilityForModules
|
||||
} from "./moduleLogic";
|
||||
uiCapability as uiCapabilityForModules } from
|
||||
"./moduleLogic";
|
||||
import { hasAnyScope, hasScope } from "../utils/permissions";
|
||||
|
||||
export const shellNavItems: PlatformNavItem[] = [
|
||||
{ to: "/dashboard", label: "Dashboard", iconName: "dashboard", order: 10 }
|
||||
];
|
||||
export const fallbackDashboardNavItem: PlatformNavItem =
|
||||
{ to: "/dashboard", label: "i18n:govoplan-core.dashboard.d87f47b4", iconName: "dashboard", order: 10 };
|
||||
|
||||
export function shellNavItemsForModules(modules: PlatformWebModule[]): PlatformNavItem[] {
|
||||
return moduleInstalledForModules("dashboard", modules) ? [] : [fallbackDashboardNavItem];
|
||||
}
|
||||
|
||||
|
||||
const localModules: PlatformWebModule[] = installedWebModules;
|
||||
const remoteModuleCache = new Map<string, Promise<PlatformWebModule | null>>();
|
||||
@@ -104,19 +108,19 @@ export function resolveInstalledWebModules(platformModules: PlatformModuleInfo[]
|
||||
if (!platformModules?.length) return localModules;
|
||||
|
||||
const localById = new Map(localModules.map((module) => [module.id, module]));
|
||||
return platformModules
|
||||
.filter((module) => module.enabled)
|
||||
.map((module) => {
|
||||
const local = localById.get(module.id);
|
||||
return local ? applyServerMetadata(local, module) : null;
|
||||
})
|
||||
.filter((module): module is PlatformWebModule => module !== null);
|
||||
return platformModules.
|
||||
filter((module) => module.enabled).
|
||||
map((module) => {
|
||||
const local = localById.get(module.id);
|
||||
return local ? applyServerMetadata(local, module) : null;
|
||||
}).
|
||||
filter((module): module is PlatformWebModule => module !== null);
|
||||
}
|
||||
|
||||
export async function loadRemoteWebModules(
|
||||
platformModules: PlatformModuleInfo[] | null | undefined,
|
||||
alreadyLoaded: PlatformWebModule[] = resolveInstalledWebModules(platformModules)
|
||||
): Promise<PlatformWebModule[]> {
|
||||
platformModules: PlatformModuleInfo[] | null | undefined,
|
||||
alreadyLoaded: PlatformWebModule[] = resolveInstalledWebModules(platformModules))
|
||||
: Promise<PlatformWebModule[]> {
|
||||
if (!platformModules?.length) return [];
|
||||
const loadedIds = new Set(alreadyLoaded.map((module) => module.id));
|
||||
const candidates = platformModules.filter((module) => {
|
||||
@@ -147,7 +151,7 @@ async function loadRemoteWebModule(info: PlatformModuleInfo): Promise<PlatformWe
|
||||
const manifestBuffer = await manifestResponse.arrayBuffer();
|
||||
if (frontend.asset_manifest_integrity) {
|
||||
const ok = await verifyIntegrity(manifestBuffer, frontend.asset_manifest_integrity);
|
||||
if (!ok) throw new Error("manifest integrity check failed");
|
||||
if (!ok) throw new Error("i18n:govoplan-core.manifest_integrity_check_failed.6e3913bc");
|
||||
}
|
||||
const manifestText = new TextDecoder().decode(manifestBuffer);
|
||||
if (frontend.asset_manifest_signature && frontend.asset_manifest_public_key_id) {
|
||||
@@ -156,28 +160,28 @@ async function loadRemoteWebModule(info: PlatformModuleInfo): Promise<PlatformWe
|
||||
frontend.asset_manifest_signature,
|
||||
frontend.asset_manifest_public_key_id
|
||||
);
|
||||
if (signatureOk === false) throw new Error("manifest signature check failed");
|
||||
if (signatureOk === null && !frontend.asset_manifest_integrity) throw new Error("manifest signature key is not trusted by this shell");
|
||||
if (signatureOk === false) throw new Error("i18n:govoplan-core.manifest_signature_check_failed.a9860176");
|
||||
if (signatureOk === null && !frontend.asset_manifest_integrity) throw new Error("i18n:govoplan-core.manifest_signature_key_is_not_trusted_by_this_sh.3bb2f629");
|
||||
} else if (!frontend.asset_manifest_integrity) {
|
||||
throw new Error("remote manifests need an integrity hash or a verifiable signature");
|
||||
throw new Error("i18n:govoplan-core.remote_manifests_need_an_integrity_hash_or_a_ver.bfbb7daa");
|
||||
}
|
||||
const manifest = JSON.parse(manifestText) as RemoteAssetManifest;
|
||||
const contractVersion = manifest.contractVersion ?? frontend.asset_manifest_contract_version ?? "1";
|
||||
if (contractVersion !== "1") throw new Error(`unsupported remote asset contract ${contractVersion}`);
|
||||
if (manifest.moduleId && manifest.moduleId !== info.id) throw new Error(`manifest module id ${manifest.moduleId} does not match ${info.id}`);
|
||||
if (!manifest.entry) throw new Error("remote manifest has no entry");
|
||||
if (!manifest.entryIntegrity) throw new Error("remote entry needs an integrity hash");
|
||||
if (!manifest.entry) throw new Error("i18n:govoplan-core.remote_manifest_has_no_entry.8220dbc9");
|
||||
if (!manifest.entryIntegrity) throw new Error("i18n:govoplan-core.remote_entry_needs_an_integrity_hash.3b69ae0a");
|
||||
|
||||
const entryUrl = absoluteModuleAssetUrl(manifest.entry, manifestUrl);
|
||||
const entryResponse = await fetch(entryUrl, { credentials: "same-origin" });
|
||||
if (!entryResponse.ok) throw new Error(`entry fetch failed with HTTP ${entryResponse.status}`);
|
||||
const entryBuffer = await entryResponse.arrayBuffer();
|
||||
if (!(await verifyIntegrity(entryBuffer, manifest.entryIntegrity))) throw new Error("entry integrity check failed");
|
||||
if (!(await verifyIntegrity(entryBuffer, manifest.entryIntegrity))) throw new Error("i18n:govoplan-core.entry_integrity_check_failed.1d8f6b89");
|
||||
const blobUrl = URL.createObjectURL(new Blob([entryBuffer], { type: "text/javascript" }));
|
||||
try {
|
||||
const imported = await import(/* @vite-ignore */ blobUrl) as Record<string, unknown>;
|
||||
const imported = (await import(/* @vite-ignore */blobUrl)) as Record<string, unknown>;
|
||||
const exported = manifest.moduleExport ? imported[manifest.moduleExport] : imported.default ?? imported.module;
|
||||
if (!isPlatformWebModule(exported)) throw new Error("entry did not export a PlatformWebModule");
|
||||
if (!isPlatformWebModule(exported)) throw new Error("i18n:govoplan-core.entry_did_not_export_a_platformwebmodule.9a157741");
|
||||
if (exported.id !== info.id) throw new Error(`entry module id ${exported.id} does not match ${info.id}`);
|
||||
return applyServerMetadata(exported, info);
|
||||
} finally {
|
||||
@@ -278,10 +282,16 @@ export function routeContributionsForModules(modules: PlatformWebModule[]) {
|
||||
return routeContributionsForModuleList(modules);
|
||||
}
|
||||
|
||||
export function dashboardWidgetsForModules(modules: PlatformWebModule[] = localModules): DashboardWidgetContribution[] {
|
||||
return uiCapabilitiesForModules<DashboardWidgetsUiCapability>("dashboard.widgets", modules).
|
||||
flatMap((capability) => capability.widgets ?? []).
|
||||
sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
||||
}
|
||||
|
||||
export function navItemsForModules(modules: PlatformWebModule[]): PlatformNavItem[] {
|
||||
return [...shellNavItems, ...modules.flatMap((module) => module.navItems ?? [])]
|
||||
.map(resolveNavItemIcon)
|
||||
.sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
||||
return [...shellNavItemsForModules(modules), ...modules.flatMap((module) => module.navItems ?? [])].
|
||||
map(resolveNavItemIcon).
|
||||
sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
||||
}
|
||||
|
||||
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = localModules): PlatformNavItem[] {
|
||||
@@ -293,7 +303,5 @@ export function visibleNavItems(auth: AuthInfo | null | undefined, modules: Plat
|
||||
}
|
||||
|
||||
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = localModules): string {
|
||||
const preferred = visibleNavItems(auth, modules).find((item) => item.to !== "/dashboard");
|
||||
if (preferred) return preferred.to;
|
||||
return "/dashboard";
|
||||
return visibleNavItems(auth, modules)[0]?.to ?? "/dashboard";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user