import { environment, LocalStorage } from "@raycast/api"; import { exec, spawn } from "child_process"; import { access, mkdir, readFile, symlink, unlink, writeFile, } from "fs/promises"; import * as os from "os"; import * as path from "path"; import { promisify } from "util"; const execAsync = promisify(exec); const COOKIE_KEY = "session_cookie"; // ─── Cookie storage ──────────────────────────────────────────────────────── export async function getStoredCookie(): Promise { return LocalStorage.getItem(COOKIE_KEY); } export async function storeCookie(cookie: string): Promise { await LocalStorage.setItem(COOKIE_KEY, cookie); } export async function clearCookie(): Promise { await LocalStorage.removeItem(COOKIE_KEY); } // ─── Auth browser ────────────────────────────────────────────────────────── // Opens an isolated WKWebView window via a temporary .app bundle so macOS // grants it proper window-server access. Cookie is returned through a temp file. export async function launchAuthBrowser(signInUrl: string): Promise { const binaryPath = await ensureBinary(); const appBundle = await ensureAppBundle(binaryPath); const cookieFile = path.join(os.tmpdir(), "cedarstalk-cookie.txt"); await unlink(cookieFile).catch(() => {}); // Use spawn (not execAsync) so the SAML URL isn't shell-interpreted await new Promise((resolve, reject) => { const proc = spawn( "open", ["-n", "-W", appBundle, "--args", signInUrl, cookieFile], { stdio: "ignore", }, ); proc.on("close", (code) => { // open -W exits 0 whether the app succeeded or cancelled; // we detect success by whether the cookie file was written resolve(); }); proc.on("error", reject); }); const cookie = await readFile(cookieFile, "utf-8") .then((s) => s.trim()) .catch(() => ""); await unlink(cookieFile).catch(() => {}); if (!cookie) throw new Error("Sign-in cancelled"); return cookie; } async function ensureBinary(): Promise { const swiftSrc = path.join(environment.assetsPath, "auth-browser.swift"); const binaryPath = path.join(environment.supportPath, "auth-browser"); try { await access(binaryPath); return binaryPath; } catch { await mkdir(environment.supportPath, { recursive: true }); // Compile with optimisations to avoid debug-mode assertion traps await execAsync(`swiftc -O "${swiftSrc}" -o "${binaryPath}"`); // Ad-hoc sign so macOS treats it as a trusted binary await execAsync(`codesign --sign - --force "${binaryPath}"`); return binaryPath; } } async function ensureAppBundle(binaryPath: string): Promise { const appDir = path.join(os.tmpdir(), "CedarStalkAuth.app"); const macosDir = path.join(appDir, "Contents", "MacOS"); const plistPath = path.join(appDir, "Contents", "Info.plist"); const bundledBinary = path.join(macosDir, "CedarStalkAuth"); await mkdir(macosDir, { recursive: true }); // Always recreate the symlink so it tracks the current binary path await unlink(bundledBinary).catch(() => {}); await symlink(binaryPath, bundledBinary); await writeFile( plistPath, ` CFBundlePackageTypeAPPL CFBundleExecutableCedarStalkAuth CFBundleIdentifiersh.dunkirk.cedarstalk.auth CFBundleNameCedarStalk Auth NSPrincipalClassNSApplication NSHighResolutionCapable LSMinimumSystemVersion13.0 `, ); return appDir; }