cedarstalking with keyboard shortcuts
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2e1496913191a9d1b76e87d1ffe7cc9d36e2cc4b 116 lines 4.1 kB view raw
1import { environment, LocalStorage } from "@raycast/api"; 2import { exec, spawn } from "child_process"; 3import { 4 access, 5 mkdir, 6 readFile, 7 symlink, 8 unlink, 9 writeFile, 10} from "fs/promises"; 11import * as os from "os"; 12import * as path from "path"; 13import { promisify } from "util"; 14 15const execAsync = promisify(exec); 16const COOKIE_KEY = "session_cookie"; 17 18// ─── Cookie storage ──────────────────────────────────────────────────────── 19 20export async function getStoredCookie(): Promise<string | undefined> { 21 return LocalStorage.getItem<string>(COOKIE_KEY); 22} 23 24export async function storeCookie(cookie: string): Promise<void> { 25 await LocalStorage.setItem(COOKIE_KEY, cookie); 26} 27 28export async function clearCookie(): Promise<void> { 29 await LocalStorage.removeItem(COOKIE_KEY); 30} 31 32// ─── Auth browser ────────────────────────────────────────────────────────── 33 34// Opens an isolated WKWebView window via a temporary .app bundle so macOS 35// grants it proper window-server access. Cookie is returned through a temp file. 36export async function launchAuthBrowser(signInUrl: string): Promise<string> { 37 const binaryPath = await ensureBinary(); 38 const appBundle = await ensureAppBundle(binaryPath); 39 const cookieFile = path.join(os.tmpdir(), "cedarstalk-cookie.txt"); 40 41 await unlink(cookieFile).catch(() => {}); 42 43 // Use spawn (not execAsync) so the SAML URL isn't shell-interpreted 44 await new Promise<void>((resolve, reject) => { 45 const proc = spawn( 46 "open", 47 ["-n", "-W", appBundle, "--args", signInUrl, cookieFile], 48 { 49 stdio: "ignore", 50 }, 51 ); 52 proc.on("close", (code) => { 53 // open -W exits 0 whether the app succeeded or cancelled; 54 // we detect success by whether the cookie file was written 55 resolve(); 56 }); 57 proc.on("error", reject); 58 }); 59 60 const cookie = await readFile(cookieFile, "utf-8") 61 .then((s) => s.trim()) 62 .catch(() => ""); 63 await unlink(cookieFile).catch(() => {}); 64 65 if (!cookie) throw new Error("Sign-in cancelled"); 66 return cookie; 67} 68 69async function ensureBinary(): Promise<string> { 70 const swiftSrc = path.join(environment.assetsPath, "auth-browser.swift"); 71 const binaryPath = path.join(environment.supportPath, "auth-browser"); 72 73 try { 74 await access(binaryPath); 75 return binaryPath; 76 } catch { 77 await mkdir(environment.supportPath, { recursive: true }); 78 // Compile with optimisations to avoid debug-mode assertion traps 79 await execAsync(`swiftc -O "${swiftSrc}" -o "${binaryPath}"`); 80 // Ad-hoc sign so macOS treats it as a trusted binary 81 await execAsync(`codesign --sign - --force "${binaryPath}"`); 82 return binaryPath; 83 } 84} 85 86async function ensureAppBundle(binaryPath: string): Promise<string> { 87 const appDir = path.join(os.tmpdir(), "CedarStalkAuth.app"); 88 const macosDir = path.join(appDir, "Contents", "MacOS"); 89 const plistPath = path.join(appDir, "Contents", "Info.plist"); 90 const bundledBinary = path.join(macosDir, "CedarStalkAuth"); 91 92 await mkdir(macosDir, { recursive: true }); 93 94 // Always recreate the symlink so it tracks the current binary path 95 await unlink(bundledBinary).catch(() => {}); 96 await symlink(binaryPath, bundledBinary); 97 98 await writeFile( 99 plistPath, 100 `<?xml version="1.0" encoding="UTF-8"?> 101<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 102<plist version="1.0"> 103<dict> 104 <key>CFBundlePackageType</key><string>APPL</string> 105 <key>CFBundleExecutable</key><string>CedarStalkAuth</string> 106 <key>CFBundleIdentifier</key><string>sh.dunkirk.cedarstalk.auth</string> 107 <key>CFBundleName</key><string>CedarStalk Auth</string> 108 <key>NSPrincipalClass</key><string>NSApplication</string> 109 <key>NSHighResolutionCapable</key><true/> 110 <key>LSMinimumSystemVersion</key><string>13.0</string> 111</dict> 112</plist>`, 113 ); 114 115 return appDir; 116}