import type { ActiveObjectReference, AuthInfo, EffectiveViewProjection, QuickAccessLaunchContext, QuickAccessResult } from "../types"; import type { TemporalDataSelection } from "./temporal"; export const QUICK_ACCESS_LAUNCH_CONTEXT_VERSION = "2" as const; export const QUICK_ACCESS_REFERENCE_CONTRACT_VERSION = "1" as const; export const QUICK_ACCESS_RESULT_CONTRACT_VERSION = "1" as const; export const QUICK_ACCESS_LAUNCH_STATE_KEY = "govoplanQuickAccessLaunch"; export const QUICK_ACCESS_RESULT_EVENT = "govoplan:quick-access-result"; export type QuickAccessLaunchContextInput = { pathname: string; search?: string; hash?: string; historyIndex?: number | null; auth: AuthInfo; activeObject?: ActiveObjectReference | null; temporalContext: TemporalDataSelection; viewContext?: EffectiveViewProjection | null; }; export function createQuickAccessLaunchContext({ pathname, search = "", hash = "", historyIndex = null, auth, activeObject = null, temporalContext, viewContext = null }: QuickAccessLaunchContextInput): QuickAccessLaunchContext { const tenant = auth.active_tenant ?? auth.tenant; const principal = auth.principal; return { contractVersion: QUICK_ACCESS_LAUNCH_CONTEXT_VERSION, referenceContractVersion: QUICK_ACCESS_REFERENCE_CONTRACT_VERSION, origin: { pathname: normalizePathname(pathname), search: normalizeSearch(search), hash: normalizeHash(hash), historyIndex: Number.isInteger(historyIndex) ? historyIndex ?? null : null }, tenantId: tenant.id, accountId: auth.user.account_id, activeObject: activeObject?.tenantId === tenant.id ? activeObject : null, actingContext: principal?.acting_assignment_id || principal?.acting_for_account_id ? { assignmentId: principal.acting_assignment_id ?? null, actingForAccountId: principal.acting_for_account_id ?? null } : null, temporalContext: { validityMode: temporalContext.validityMode, validAt: temporalContext.validAt ?? null, recordedAt: temporalContext.recordedAt ?? null }, viewContext: viewContext?.activeViewId ? { viewId: viewContext.activeViewId, revisionId: viewContext.activeRevisionId, name: viewContext.activeViewName, recommendedToolIds: viewContext.presentation?.quickAccessRecommendedToolIds ?? [], focusedToolIds: viewContext.presentation?.quickAccessFocusedToolIds ?? [] } : null }; } export function quickAccessLaunchState( context: QuickAccessLaunchContext ): Record { return { [QUICK_ACCESS_LAUNCH_STATE_KEY]: context }; } export function quickAccessLaunchContextFromState( value: unknown ): QuickAccessLaunchContext | null { if (!isRecord(value)) return null; const candidate = value[QUICK_ACCESS_LAUNCH_STATE_KEY]; if ( !isRecord(candidate) || candidate.contractVersion !== QUICK_ACCESS_LAUNCH_CONTEXT_VERSION || candidate.referenceContractVersion !== QUICK_ACCESS_REFERENCE_CONTRACT_VERSION ) { return null; } if (!isRecord(candidate.origin) || typeof candidate.origin.pathname !== "string") { return null; } if ( typeof candidate.tenantId !== "string" || typeof candidate.accountId !== "string" || !candidate.origin.pathname.startsWith("/") ) { return null; } return candidate as QuickAccessLaunchContext; } export function quickAccessReturnPath(context: QuickAccessLaunchContext): string { return `${context.origin.pathname}${context.origin.search}${context.origin.hash}`; } export function dispatchQuickAccessResult( toolId: string, launchContext: QuickAccessLaunchContext, result: QuickAccessResult, returnedReferenceKinds?: readonly string[] ): boolean { if ( typeof window === "undefined" || !isQuickAccessResultAllowed(result, launchContext, returnedReferenceKinds) ) return false; window.dispatchEvent(new CustomEvent(QUICK_ACCESS_RESULT_EVENT, { detail: { toolId, launchContext, result } })); return true; } export function isQuickAccessResultAllowed( result: QuickAccessResult, launchContext: QuickAccessLaunchContext, returnedReferenceKinds?: readonly string[] ): boolean { if (result.contractVersion !== QUICK_ACCESS_RESULT_CONTRACT_VERSION) return false; if (result.outcome === "cancelled") { return result.action === undefined && result.reference === undefined && (result.reason === undefined || (["user", "dismissed", "unavailable", "failed"] as const).includes(result.reason)); } if (result.outcome !== "completed") return false; if (!(["created", "selected", "updated", "completed"] as const).includes(result.action)) return false; if (!result.reference) return true; if ( result.reference.tenantId !== launchContext.tenantId || !result.reference.ownerModule || !result.reference.kind || !result.reference.objectId || (result.reference.path !== undefined && result.reference.path !== null && (!result.reference.path.startsWith("/") || result.reference.path.startsWith("//"))) ) return false; if (returnedReferenceKinds) { const returnedKind = `${result.reference.ownerModule}.${result.reference.kind}`; if (!returnedReferenceKinds.includes(returnedKind)) return false; } return true; } function normalizePathname(value: string): string { return value.startsWith("/") ? value : "/"; } function normalizeSearch(value: string): string { return !value ? "" : value.startsWith("?") ? value : ""; } function normalizeHash(value: string): string { return !value ? "" : value.startsWith("#") ? value : ""; } function isRecord(value: unknown): value is Record { return Boolean(value && typeof value === "object" && !Array.isArray(value)); }