Skip to content

Commit 99adc67

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 99adc67

File tree

4 files changed

+19
-37
lines changed

4 files changed

+19
-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: 9 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,34 @@ export function registerPosthogIpc(): void {
3534
},
3635
);
3736

38-
// Fetch and parse S3 logs
3937
ipcMain.handle(
4038
"fetch-s3-logs",
4139
async (
4240
_event: IpcMainInvokeEvent,
4341
logUrl: string,
44-
): Promise<AgentEvent[]> => {
42+
): Promise<string | null> => {
4543
try {
4644
log.debug("Fetching S3 logs from:", logUrl);
4745
const response = await fetch(logUrl);
4846

4947
// 404 is expected for new task runs - file doesn't exist yet
5048
if (response.status === 404) {
51-
return [];
49+
return null;
5250
}
5351

5452
if (!response.ok) {
55-
throw new Error(
56-
`Failed to fetch logs: ${response.status} ${response.statusText}`,
53+
log.warn(
54+
"Failed to fetch S3 logs:",
55+
response.status,
56+
response.statusText,
5757
);
58+
return null;
5859
}
5960

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);
61+
return await response.text();
7962
} catch (error) {
8063
log.error("Failed to fetch S3 logs:", error);
81-
throw error;
64+
return null;
8265
}
8366
},
8467
);

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/// <reference path="../../../types/electron.d.ts" />
12
import type { SessionNotification } from "@agentclientprotocol/sdk";
23

34
export interface StoredLogEntry {
@@ -26,14 +27,16 @@ 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+
const content = await window.electronAPI.fetchS3Logs(logUrl);
3436

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

3841
const notifications: SessionNotification[] = [];
3942
const rawEntries: StoredLogEntry[] = [];
@@ -47,7 +50,6 @@ export async function fetchSessionLogs(
4750
// - Request (has id + method) = client → agent
4851
// - Response (has id + result/error) = agent → client
4952
// - Notification (has method, no id) = agent → client
50-
// TODO: Check if this is correct.
5153
const msg = stored.notification;
5254
if (msg) {
5355
const hasId = msg.id !== undefined;
@@ -94,7 +96,6 @@ export async function fetchSessionLogs(
9496

9597
return { notifications, rawEntries, sdkSessionId };
9698
} catch {
97-
// Network error or other failure
9899
return { notifications: [], rawEntries: [] };
99100
}
100101
}

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)