Skip to content

Commit 26d135e

Browse files
authored
feat: retry penpal connection if failed (#2581)
* fix: improve penpal connection retry logic with exponential backoff - Add retry limit (max 3 attempts) before iframe reload - Implement exponential backoff (1s, 2s, 4s delays) - Consolidate error handling to avoid double retries - Reset retry count on successful connection - Add detailed logging for retry attempts
1 parent a55bded commit 26d135e

File tree

2 files changed

+96
-57
lines changed

2 files changed

+96
-57
lines changed

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- This is a Bun workspace, only use bun, not npm
2+
- Unit tests can be ran with bun test

apps/web/client/src/app/project/[id]/_components/canvas/frame/web-frame.tsx

Lines changed: 94 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export const WebFrameComponent = observer(
4444
const zoomLevel = useRef(1);
4545
const isConnecting = useRef(false);
4646
const connectionRef = useRef<ReturnType<typeof connect> | null>(null);
47+
const retryCount = useRef(0);
48+
const maxRetries = 3;
49+
const baseDelay = 1000;
4750
const [penpalChild, setPenpalChild] = useState<PenpalChildMethods | null>(null);
4851

4952
const undebouncedReloadIframe = () => {
@@ -60,75 +63,109 @@ export const WebFrameComponent = observer(
6063
leading: true,
6164
});
6265

63-
const setupPenpalConnection = () => {
64-
if (!iframeRef.current?.contentWindow) {
65-
console.error('No iframe found');
66-
return;
67-
}
68-
69-
if (isConnecting.current) {
70-
console.log(
71-
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Connection already in progress`,
66+
const retrySetupPenpalConnection = (error?: Error) => {
67+
if (retryCount.current >= maxRetries) {
68+
console.error(
69+
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Max retries (${maxRetries}) reached, reloading iframe`,
70+
error,
7271
);
72+
retryCount.current = 0;
73+
reloadIframe();
7374
return;
7475
}
75-
isConnecting.current = true;
7676

77-
// Destroy any existing connection before creating a new one
78-
if (connectionRef.current) {
79-
connectionRef.current.destroy();
80-
connectionRef.current = null;
81-
}
77+
retryCount.current += 1;
78+
const delay = baseDelay * Math.pow(2, retryCount.current - 1);
79+
80+
console.log(
81+
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Retrying connection attempt ${retryCount.current}/${maxRetries} in ${delay}ms`,
82+
);
8283

83-
const messenger = new WindowMessenger({
84-
remoteWindow: iframeRef.current.contentWindow,
85-
allowedOrigins: ['*'],
86-
});
87-
88-
const connection = connect({
89-
messenger,
90-
methods: {
91-
getFrameId: () => frame.id,
92-
onWindowMutated: () => {
93-
editorEngine.frameEvent.handleWindowMutated();
94-
},
95-
onWindowResized: () => {
96-
editorEngine.frameEvent.handleWindowResized();
97-
},
98-
onDomProcessed: (data: { layerMap: Record<string, any>; rootNode: any }) => {
99-
editorEngine.frameEvent.handleDomProcessed(frame.id, data);
100-
},
101-
} satisfies PenpalParentMethods,
102-
});
84+
setTimeout(() => {
85+
setupPenpalConnection();
86+
}, delay);
87+
};
10388

104-
// Store the connection reference
105-
connectionRef.current = connection;
89+
const setupPenpalConnection = () => {
90+
try {
91+
if (!iframeRef.current?.contentWindow) {
92+
console.error('No iframe found');
93+
throw new Error('No iframe found');
94+
}
10695

107-
connection.promise.then((child) => {
108-
isConnecting.current = false;
109-
if (!child) {
110-
console.error(
111-
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Failed to setup penpal connection: child is null`,
96+
if (isConnecting.current) {
97+
console.log(
98+
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Connection already in progress`,
11299
);
113-
reloadIframe();
114100
return;
115101
}
116-
const remote = child as unknown as PenpalChildMethods;
117-
setPenpalChild(remote);
118-
remote.setFrameId(frame.id);
119-
remote.handleBodyReady();
120-
remote.processDom();
121-
console.log(`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Penpal connection set `);
122-
});
102+
isConnecting.current = true;
103+
104+
// Destroy any existing connection before creating a new one
105+
if (connectionRef.current) {
106+
connectionRef.current.destroy();
107+
connectionRef.current = null;
108+
}
109+
110+
const messenger = new WindowMessenger({
111+
remoteWindow: iframeRef.current.contentWindow,
112+
allowedOrigins: ['*'],
113+
});
114+
115+
const connection = connect({
116+
messenger,
117+
methods: {
118+
getFrameId: () => frame.id,
119+
onWindowMutated: () => {
120+
editorEngine.frameEvent.handleWindowMutated();
121+
},
122+
onWindowResized: () => {
123+
editorEngine.frameEvent.handleWindowResized();
124+
},
125+
onDomProcessed: (data: { layerMap: Record<string, any>; rootNode: any }) => {
126+
editorEngine.frameEvent.handleDomProcessed(frame.id, data);
127+
},
128+
} satisfies PenpalParentMethods,
129+
});
123130

124-
connection.promise.catch((error) => {
131+
// Store the connection reference
132+
connectionRef.current = connection;
133+
134+
connection.promise
135+
.then((child) => {
136+
isConnecting.current = false;
137+
if (!child) {
138+
const error = new Error('Failed to setup penpal connection: child is null');
139+
console.error(
140+
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - ${error.message}`,
141+
);
142+
retrySetupPenpalConnection(error);
143+
return;
144+
}
145+
146+
// Reset retry count on successful connection
147+
retryCount.current = 0;
148+
149+
const remote = child as unknown as PenpalChildMethods;
150+
setPenpalChild(remote);
151+
remote.setFrameId(frame.id);
152+
remote.handleBodyReady();
153+
remote.processDom();
154+
console.log(`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Penpal connection set `);
155+
})
156+
.catch((error) => {
157+
isConnecting.current = false;
158+
console.error(
159+
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Failed to setup penpal connection:`,
160+
error,
161+
);
162+
retrySetupPenpalConnection(error);
163+
});
164+
} catch (error) {
125165
isConnecting.current = false;
126-
console.error(
127-
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Failed to setup penpal connection:`,
128-
error,
129-
);
130-
reloadIframe();
131-
});
166+
console.error('Failed to setup penpal connection', error);
167+
retrySetupPenpalConnection(error as Error);
168+
}
132169
};
133170

134171
const promisifyMethod = <T extends (...args: any[]) => any>(

0 commit comments

Comments
 (0)