Skip to content

Commit 9fd98af

Browse files
jonathanlabclaude
andcommitted
fix: Bypass CORS for S3 log fetching in renderer
Moves S3 log fetching from the renderer process to the main process via IPC to bypass CORS restrictions. The renderer was blocked by CORS when fetching presigned S3 URLs from localhost in development mode. The main process (Node.js) is not subject to CORS, so fetching the raw text there and passing it to the renderer via IPC fixes the issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 4e4eb4a commit 9fd98af

File tree

4 files changed

+21
-37
lines changed

4 files changed

+21
-37
lines changed

apps/array/src/main/preload.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { ContentBlock } from "@agentclientprotocol/sdk";
2-
import type { AgentEvent } from "@posthog/agent";
32
import { contextBridge, type IpcRendererEvent, ipcRenderer } from "electron";
43
import type {
54
CreateWorkspaceOptions,
@@ -69,7 +68,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
6968
ipcRenderer.invoke("store-api-key", apiKey),
7069
retrieveApiKey: (encryptedKey: string): Promise<string | null> =>
7170
ipcRenderer.invoke("retrieve-api-key", encryptedKey),
72-
fetchS3Logs: (logUrl: string): Promise<AgentEvent[]> =>
71+
fetchS3Logs: (logUrl: string): Promise<string | null> =>
7372
ipcRenderer.invoke("fetch-s3-logs", logUrl),
7473
rendererStore: {
7574
getItem: (key: string): Promise<string | null> =>

apps/array/src/main/services/posthog.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { type AgentEvent, parseAgentEvents } from "@posthog/agent";
21
import { type IpcMainInvokeEvent, ipcMain, safeStorage } from "electron";
32
import { logger } from "../lib/logger";
43

@@ -35,50 +34,35 @@ export function registerPosthogIpc(): void {
3534
},
3635
);
3736

38-
// Fetch and parse S3 logs
37+
// Fetch S3 logs - returns raw text to bypass CORS in renderer
3938
ipcMain.handle(
4039
"fetch-s3-logs",
4140
async (
4241
_event: IpcMainInvokeEvent,
4342
logUrl: string,
44-
): Promise<AgentEvent[]> => {
43+
): Promise<string | null> => {
4544
try {
4645
log.debug("Fetching S3 logs from:", logUrl);
4746
const response = await fetch(logUrl);
4847

4948
// 404 is expected for new task runs - file doesn't exist yet
5049
if (response.status === 404) {
51-
return [];
50+
return null;
5251
}
5352

5453
if (!response.ok) {
55-
throw new Error(
56-
`Failed to fetch logs: ${response.status} ${response.statusText}`,
54+
log.warn(
55+
"Failed to fetch S3 logs:",
56+
response.status,
57+
response.statusText,
5758
);
59+
return null;
5860
}
5961

60-
const content = await response.text();
61-
62-
if (!content.trim()) {
63-
return [];
64-
}
65-
66-
const rawEntries = content
67-
.trim()
68-
.split("\n")
69-
.map((line) => {
70-
try {
71-
return JSON.parse(line);
72-
} catch {
73-
return null;
74-
}
75-
})
76-
.filter(Boolean);
77-
78-
return parseAgentEvents(rawEntries);
62+
return await response.text();
7963
} catch (error) {
8064
log.error("Failed to fetch S3 logs:", error);
81-
throw error;
65+
return null;
8266
}
8367
},
8468
);

apps/array/src/renderer/features/sessions/utils/parseSessionLogs.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SessionNotification } from "@agentclientprotocol/sdk";
2+
import "@renderer/types/electron";
23

34
export interface StoredLogEntry {
45
type: string;
@@ -26,14 +27,17 @@ export interface ParsedSessionLogs {
2627
export async function fetchSessionLogs(
2728
logUrl: string,
2829
): Promise<ParsedSessionLogs> {
29-
if (!logUrl) return { notifications: [], rawEntries: [] };
30+
if (!logUrl) {
31+
return { notifications: [], rawEntries: [] };
32+
}
3033

3134
try {
32-
const response = await fetch(logUrl);
33-
if (!response.ok) return { notifications: [], rawEntries: [] };
35+
// Use IPC to fetch from main process to bypass CORS
36+
const content = await window.electronAPI.fetchS3Logs(logUrl);
3437

35-
const content = await response.text();
36-
if (!content.trim()) return { notifications: [], rawEntries: [] };
38+
if (!content?.trim()) {
39+
return { notifications: [], rawEntries: [] };
40+
}
3741

3842
const notifications: SessionNotification[] = [];
3943
const rawEntries: StoredLogEntry[] = [];
@@ -47,7 +51,6 @@ export async function fetchSessionLogs(
4751
// - Request (has id + method) = client → agent
4852
// - Response (has id + result/error) = agent → client
4953
// - Notification (has method, no id) = agent → client
50-
// TODO: Check if this is correct.
5154
const msg = stored.notification;
5255
if (msg) {
5356
const hasId = msg.id !== undefined;
@@ -94,7 +97,6 @@ export async function fetchSessionLogs(
9497

9598
return { notifications, rawEntries, sdkSessionId };
9699
} catch {
97-
// Network error or other failure
98100
return { notifications: [], rawEntries: [] };
99101
}
100102
}

apps/array/src/renderer/types/electron.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type {
55
TabContextMenuResult,
66
TaskContextMenuResult,
77
} from "@main/services/contextMenu.types";
8-
import type { AgentEvent } from "@posthog/agent";
98
import type { ContentBlock } from "@agentclientprotocol/sdk";
109
import type {
1110
ChangedFile,
@@ -25,7 +24,7 @@ declare global {
2524
interface IElectronAPI {
2625
storeApiKey: (apiKey: string) => Promise<string>;
2726
retrieveApiKey: (encryptedKey: string) => Promise<string | null>;
28-
fetchS3Logs: (logUrl: string) => Promise<AgentEvent[]>;
27+
fetchS3Logs: (logUrl: string) => Promise<string | null>;
2928
rendererStore: {
3029
getItem: (key: string) => Promise<string | null>;
3130
setItem: (key: string, value: string) => Promise<void>;

0 commit comments

Comments
 (0)