* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
219 lines
6.6 KiB
TypeScript
219 lines
6.6 KiB
TypeScript
import { Platform } from "react-native";
|
|
import { getElectronHost } from "@/desktop/electron/host";
|
|
import type { BrowserKeyboardPolicy } from "@/desktop/browser/shortcuts";
|
|
import type { SessionInboundMessage, SessionOutboundMessage } from "@getpaseo/protocol/messages";
|
|
|
|
type BrowserAutomationExecuteRequest = Extract<
|
|
SessionOutboundMessage,
|
|
{ type: "browser.automation.execute.request" }
|
|
>;
|
|
type BrowserAutomationExecuteResponse = Extract<
|
|
SessionInboundMessage,
|
|
{ type: "browser.automation.execute.response" }
|
|
>;
|
|
|
|
export type DesktopNotificationPermission = "granted" | "denied" | "default";
|
|
|
|
export interface DesktopDialogAskOptions {
|
|
title?: string;
|
|
okLabel?: string;
|
|
cancelLabel?: string;
|
|
kind?: "info" | "warning" | "error";
|
|
}
|
|
|
|
export interface DesktopDialogOpenOptions {
|
|
title?: string;
|
|
defaultPath?: string;
|
|
directory?: boolean;
|
|
createDirectory?: boolean;
|
|
multiple?: boolean;
|
|
filters?: Array<{
|
|
name: string;
|
|
extensions: string[];
|
|
}>;
|
|
}
|
|
|
|
export interface DesktopDialogAskWithCheckboxOptions extends DesktopDialogAskOptions {
|
|
checkboxLabel: string;
|
|
checkboxChecked?: boolean;
|
|
}
|
|
|
|
export interface DesktopDialogAskWithCheckboxResult {
|
|
confirmed: boolean;
|
|
dontAskAgain: boolean;
|
|
}
|
|
|
|
export interface DesktopDialogBridge {
|
|
ask?: (message: string, options?: DesktopDialogAskOptions) => Promise<boolean>;
|
|
askWithCheckbox?: (
|
|
message: string,
|
|
options: DesktopDialogAskWithCheckboxOptions,
|
|
) => Promise<DesktopDialogAskWithCheckboxResult>;
|
|
open?: (options?: DesktopDialogOpenOptions) => Promise<string | string[] | null>;
|
|
}
|
|
|
|
export interface DesktopNotificationBridge {
|
|
isSupported?: () => Promise<boolean>;
|
|
sendNotification?: (
|
|
payload: string | { title: string; body?: string; data?: Record<string, unknown> },
|
|
) => Promise<boolean>;
|
|
}
|
|
|
|
export interface DesktopOpenerBridge {
|
|
openUrl?: (url: string) => Promise<void>;
|
|
}
|
|
|
|
export interface DesktopEditorTargetDescriptor {
|
|
id: string;
|
|
label: string;
|
|
kind: "editor" | "file-manager";
|
|
icon: { kind: "image"; dataUrl: string } | { kind: "symbol"; name: "folder" | "terminal" };
|
|
}
|
|
|
|
export interface DesktopEditorOpenTargetInput {
|
|
editorId: string;
|
|
workspacePath: string;
|
|
filePath?: string;
|
|
line?: number;
|
|
column?: number;
|
|
}
|
|
|
|
export interface DesktopEditorBridge {
|
|
listTargets?: () => Promise<DesktopEditorTargetDescriptor[]>;
|
|
openTarget?: (input: DesktopEditorOpenTargetInput) => Promise<void>;
|
|
}
|
|
|
|
export interface DesktopWebUtilsBridge {
|
|
getPathForFile?: (file: File) => string;
|
|
}
|
|
|
|
export interface DesktopMenuBridge {
|
|
showContextMenu?: (input?: { kind?: "terminal"; hasSelection?: boolean }) => Promise<void>;
|
|
setCapturingShortcut?: (capturing: boolean) => Promise<void>;
|
|
}
|
|
|
|
export interface DesktopWindowControlsOverlayUpdate {
|
|
height?: number;
|
|
backgroundColor?: string;
|
|
foregroundColor?: string;
|
|
trafficLightOffsetY?: number;
|
|
}
|
|
|
|
export interface DesktopWindowBridge {
|
|
label?: string;
|
|
toggleMaximize?: () => Promise<void>;
|
|
setFullscreen?: (fullscreen: boolean) => Promise<void>;
|
|
isFullscreen?: () => Promise<boolean>;
|
|
updateWindowControls?: (update: DesktopWindowControlsOverlayUpdate) => Promise<void>;
|
|
onResized?: <TEvent = unknown>(
|
|
handler: (event: TEvent) => void,
|
|
) => Promise<() => void> | (() => void);
|
|
setBadgeCount?: (count?: number) => Promise<void>;
|
|
onDragDropEvent?: <TEvent = unknown>(
|
|
handler: (event: TEvent) => void,
|
|
) => Promise<() => void> | (() => void);
|
|
}
|
|
|
|
export interface DesktopWindowModuleBridge {
|
|
openNew?: (options?: { pendingOpenProjectPath?: string | null }) => Promise<void>;
|
|
getCurrentWindow?: () => DesktopWindowBridge;
|
|
}
|
|
|
|
export interface DesktopEventsBridge {
|
|
on?: (event: string, handler: (payload: unknown) => void) => Promise<() => void> | (() => void);
|
|
}
|
|
|
|
export interface DesktopAgentNavigationBridge {
|
|
ready?: () => Promise<{ serverId: string; agentId: string } | null>;
|
|
}
|
|
|
|
export type DesktopBrowserShortcutEvent =
|
|
| { browserId?: string; action: "focus-url" }
|
|
| { browserId: string; action: "new-tab" };
|
|
|
|
export interface DesktopBrowserNewTabRequestEvent {
|
|
sourceBrowserId: string;
|
|
url: string;
|
|
}
|
|
|
|
export interface DesktopAttachedBrowserRegistration {
|
|
browserId: string;
|
|
workspaceId: string;
|
|
webContentsId: number;
|
|
}
|
|
|
|
export interface DesktopBrowserBridge {
|
|
setShortcutPolicy?: (input: BrowserKeyboardPolicy) => Promise<void>;
|
|
readonly profilePartition?: string;
|
|
registerAttachedBrowser?: (input: DesktopAttachedBrowserRegistration) => Promise<void>;
|
|
unregisterWorkspaceBrowser?: (browserId: string) => Promise<void>;
|
|
setWorkspaceActiveBrowser?: (input: {
|
|
workspaceId: string;
|
|
browserId: string | null;
|
|
}) => Promise<void>;
|
|
openDevTools?: (browserId: string) => Promise<unknown>;
|
|
clearProfile?: (legacyBrowserIds: string[]) => Promise<void>;
|
|
executeAutomationCommand?: (
|
|
request: BrowserAutomationExecuteRequest,
|
|
) => Promise<BrowserAutomationExecuteResponse["payload"]>;
|
|
/** Capture a PNG screenshot of the guest viewport cropped to `rect`. */
|
|
captureElement?: (
|
|
browserId: string,
|
|
rect: { x: number; y: number; width: number; height: number },
|
|
) => Promise<string | null>;
|
|
/** Copy element text and/or an image to the system clipboard from main. */
|
|
copyElement?: (payload: { text?: string; imageDataUrl?: string }) => Promise<boolean>;
|
|
}
|
|
|
|
export interface DesktopInvokeBridge {
|
|
invoke?: (command: string, args?: Record<string, unknown>) => Promise<unknown>;
|
|
}
|
|
|
|
export interface DesktopHostBridge {
|
|
platform?: string;
|
|
invoke?: DesktopInvokeBridge["invoke"];
|
|
getPendingOpenProject?: () => Promise<string | null>;
|
|
agentNavigation?: DesktopAgentNavigationBridge;
|
|
events?: DesktopEventsBridge;
|
|
window?: DesktopWindowModuleBridge;
|
|
dialog?: DesktopDialogBridge;
|
|
notification?: DesktopNotificationBridge;
|
|
opener?: DesktopOpenerBridge;
|
|
editor?: DesktopEditorBridge;
|
|
webUtils?: DesktopWebUtilsBridge;
|
|
menu?: DesktopMenuBridge;
|
|
browser?: DesktopBrowserBridge;
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
paseoDesktop?: DesktopHostBridge;
|
|
}
|
|
}
|
|
|
|
export function getDesktopHost(): DesktopHostBridge | null {
|
|
if (Platform.OS !== "web") {
|
|
return null;
|
|
}
|
|
return getElectronHost();
|
|
}
|
|
|
|
export function isElectronRuntime(): boolean {
|
|
return getDesktopHost() !== null;
|
|
}
|
|
|
|
export function isElectronRuntimeMac(): boolean {
|
|
if (!isElectronRuntime()) {
|
|
return false;
|
|
}
|
|
if (typeof navigator === "undefined") {
|
|
return false;
|
|
}
|
|
const hostPlatform = getDesktopHost()?.platform?.toLowerCase();
|
|
if (hostPlatform === "darwin" || hostPlatform === "mac" || hostPlatform === "macos") {
|
|
return true;
|
|
}
|
|
const ua = navigator.userAgent;
|
|
return ua.includes("Mac OS") || ua.includes("Macintosh");
|
|
}
|