import { OPEN_URL_ACTION_VERSION } from "../host/constants.js"; import { type FlowActionSucceededObservationSeed, toFlowActionSucceededObservationSeed, } from "./flow-action-succeeded-observation.js"; export const POST_SUBMIT_NAVIGATION_REQUEST_RPC_METHOD = "navigation.postSubmit.request"; export const BROWSER_NAVIGATION_OPEN_RPC_METHOD = "navigation.browser.open"; export const BROWSER_NAVIGATION_AVAILABILITY_RPC_METHOD = "navigation.browser.availability"; export const BROWSER_NAVIGATION_AVAILABILITY_EVENT = "browser-navigation-availability"; export type BrowserNavigationAvailabilityPayload = { type: typeof BROWSER_NAVIGATION_AVAILABILITY_EVENT; available: boolean; explicitTargetAvailable?: boolean; hostActionLinkAvailable?: boolean; }; export type BrowserNavigationAvailabilityResult = { available: boolean; }; export const OPEN_URL_ACTION_DEFINITION = Object.freeze({ key: "open-url", version: OPEN_URL_ACTION_VERSION, }); export type BrowserNavigationOpenParams = { viewId: string; generation: number; url: string; target?: "self" | "blank"; invocationId?: string; definition?: { key: string; version: number }; flowActionSucceededSeed?: FlowActionSucceededObservationSeed; }; export const isBrowserNavigationUrl = (value: string): boolean => { try { const protocol = new URL(value).protocol; return protocol === "http:" || protocol === "https:"; } catch { return false; } }; export const isBrowserNavigationAvailabilityPayload = ( value: unknown, ): value is BrowserNavigationAvailabilityPayload => typeof value === "object" && value !== null && "type" in value && value.type === BROWSER_NAVIGATION_AVAILABILITY_EVENT && "available" in value && typeof value.available === "boolean" && (!("explicitTargetAvailable" in value) || typeof value.explicitTargetAvailable === "boolean") && (!("hostActionLinkAvailable" in value) || typeof value.hostActionLinkAvailable === "boolean"); export const isBrowserNavigationAvailabilityResult = ( value: unknown, ): value is BrowserNavigationAvailabilityResult => typeof value === "object" && value !== null && "available" in value && typeof value.available === "boolean"; export const toBrowserNavigationOpenParams = ( value: unknown, ): BrowserNavigationOpenParams | null => { if ( typeof value !== "object" || value === null || !("viewId" in value) || typeof value.viewId !== "string" || value.viewId.trim().length === 0 || !("generation" in value) || typeof value.generation !== "number" || !Number.isInteger(value.generation) || value.generation < 0 || !("url" in value) || typeof value.url !== "string" || !isBrowserNavigationUrl(value.url) || ("target" in value && value.target !== "self" && value.target !== "blank") ) { return null; } const record = value as Record; const target = "target" in value && (value.target === "self" || value.target === "blank") ? value.target : undefined; const definition = "definition" in value && typeof value.definition === "object" && value.definition !== null ? (value.definition as Record) : null; const hasExactDefinition = definition !== null && Object.keys(definition).length === 2 && Object.hasOwn(definition, "key") && Object.hasOwn(definition, "version") && definition.key === OPEN_URL_ACTION_DEFINITION.key && definition.version === OPEN_URL_ACTION_DEFINITION.version; const seed = toFlowActionSucceededObservationSeed( record.flowActionSucceededSeed, "navigation-action", ); const hasCompleteAnalyticsMetadata = typeof record.invocationId === "string" && record.invocationId.trim().length > 0 && hasExactDefinition && seed !== null; const params: BrowserNavigationOpenParams = { viewId: value.viewId, generation: value.generation, url: value.url, }; if (target !== undefined) { params.target = target; } if (hasCompleteAnalyticsMetadata) { params.invocationId = record.invocationId as string; params.definition = { ...OPEN_URL_ACTION_DEFINITION }; params.flowActionSucceededSeed = seed; } return params; };