Skip to content

Commit 735afd4

Browse files
authored
Merge pull request #1061 from writer/codex/refactor-error-handling-to-include-retry-mechanism
feat: retry core initialization on WebSocket connection failure
2 parents aa2f453 + 2ec6a9f commit 735afd4

File tree

3 files changed

+68
-28
lines changed

3 files changed

+68
-28
lines changed

src/ui/src/constants/retry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const RECONNECT_DELAY_MS = 5000;
2+
export const MAX_RETRIES = 5;
3+

src/ui/src/core/index.ts

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { loadExtensions } from "./loadExtensions";
3232
import { bigIntReplacer } from "./serializer";
3333
import { useLogger } from "@/composables/useLogger";
3434
import { readBlobAsArrayBufferJson } from "@/utils/blob";
35+
import { RECONNECT_DELAY_MS } from "@/constants/retry";
3536
import {
3637
createFileToSourceFiles,
3738
deleteFileToSourceFiles,
@@ -40,7 +41,6 @@ import {
4041
moveFileToSourceFiles,
4142
} from "./sourceFiles";
4243

43-
const RECONNECT_DELAY_MS = 5000;
4444
const KEEP_ALIVE_DELAY_MS = 60000;
4545

4646
export function generateCore() {
@@ -109,31 +109,24 @@ export function generateCore() {
109109

110110
let initData: any = null;
111111
let response: Response | null = null;
112-
const maxRetries = 5;
113-
for (let attempt = 0; attempt < maxRetries; attempt++) {
114-
try {
115-
response = await fetch("./api/init", {
116-
method: "post",
117-
cache: "no-store",
118-
headers: {
119-
"Content-Type": "application/json",
120-
},
121-
body: JSON.stringify({
122-
proposedSessionId: sessionId,
123-
}),
124-
});
112+
try {
113+
response = await fetch("./api/init", {
114+
method: "post",
115+
cache: "no-store",
116+
headers: {
117+
"Content-Type": "application/json",
118+
},
119+
body: JSON.stringify({
120+
proposedSessionId: sessionId,
121+
}),
122+
});
125123

126-
const bodyText = await response.text();
127-
initData = JSON.parse(bodyText);
128-
break;
129-
} catch {
130-
if (attempt >= maxRetries - 1) {
131-
throw new Error(
132-
`Failed to acquire initialization data. Server responded with ${response?.status ?? "no response"} status.`,
133-
);
134-
}
135-
await new Promise((r) => setTimeout(r, RECONNECT_DELAY_MS * (attempt + 1)));
136-
}
124+
const bodyText = await response.text();
125+
initData = JSON.parse(bodyText);
126+
} catch {
127+
throw new Error(
128+
`Failed to acquire initialization data. Server responded with ${response?.status ?? "no response"} status.`,
129+
);
137130
}
138131

139132
if (response.status > 400) {

src/ui/src/main.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useCollaborationManager } from "./composables/useCollaborationManager.j
1111
import { useNotesManager } from "./core/useNotesManager.js";
1212
import { CollaborationManager } from "./writerTypes.js";
1313
import { useSecretsManager } from "./core/useSecretsManager.js";
14+
import { RECONNECT_DELAY_MS, MAX_RETRIES } from "@/constants/retry";
1415

1516
const wf = generateCore();
1617

@@ -83,17 +84,54 @@ async function enableCollaboration(collaborationManager: CollaborationManager) {
8384
});
8485
}
8586

87+
async function initialise() {
88+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
89+
try {
90+
await load();
91+
return;
92+
} catch (reason) {
93+
if (attempt < MAX_RETRIES - 1) {
94+
logger.warn(
95+
`Core initialization failed (attempt ${attempt + 1}/${MAX_RETRIES}). Retrying...`,
96+
reason,
97+
);
98+
const loaderEl = document.getElementById("loading_L1");
99+
if (loaderEl && !document.getElementById("loading_message") && attempt >= 2) {
100+
const message = document.createElement("div");
101+
message.id = "loading_message";
102+
message.textContent =
103+
"We're getting things ready.\nHang tight while we connect...";
104+
message.style.cssText =
105+
"position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);margin-top:120px;text-align:center;font-family: 'Poppins','Helvetica Neue','Lucida Grande',sans-serif;color:#666;font-size:14px;line-height:1.4;max-width:400px;padding:0 20px;z-index:1000;white-space:pre-line;opacity:0;transition:opacity 0.5s ease-in;";
106+
document.body.appendChild(message);
107+
// Trigger fade-in animation
108+
setTimeout(() => {
109+
message.style.opacity = "1";
110+
}, 50);
111+
}
112+
await new Promise((r) =>
113+
setTimeout(r, RECONNECT_DELAY_MS * (attempt + 1)),
114+
);
115+
continue;
116+
}
117+
throw reason;
118+
}
119+
}
120+
}
121+
86122
logger.log("Initialising core...");
87-
load()
123+
initialise()
88124
.then(async () => {
89125
logger.log("Core initialised.");
90126
})
91127
.catch((reason) => {
92128
logger.error("Core initialisation failed.", reason);
129+
93130
const errorDiv = document.createElement("div");
94131
errorDiv.className = "error-message";
95132
errorDiv.setAttribute("role", "alert");
96-
errorDiv.style.cssText = "padding: 20px; color: #d32f2f; background: #ffebee; border: 1px solid #e57373; border-radius: 4px; margin: 20px; font-family: \"Poppins\", \"Helvetica Neue\", \"Lucida Grande\", sans-serif;";
133+
errorDiv.style.cssText =
134+
'padding: 20px; color: #d32f2f; background: #ffebee; border: 1px solid #e57373; border-radius: 4px; margin: 20px; font-family: "Poppins", "Helvetica Neue", "Lucida Grande", sans-serif;';
97135

98136
const message = document.createElement("div");
99137
message.textContent =
@@ -107,7 +145,8 @@ load()
107145

108146
const reloadButton = document.createElement("button");
109147
reloadButton.textContent = "Reload page";
110-
reloadButton.className = "WdsButton WdsButton--small WdsButton--tertiary";
148+
reloadButton.className =
149+
"WdsButton WdsButton--small WdsButton--tertiary";
111150
reloadButton.style.cssText =
112151
"margin-top: 16px; font-size: .875rem; font-weight: 600; border-radius: 300px; padding: 4px 16px; height: 32px; background: var(--wdsColorWhite); color: var(--wdsColorBlack); border-color: var(--wdsColorGray2); border-width: 1px; border-style: solid; box-shadow: var(--buttonShadow); cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; outline: none; position: relative; overflow: hidden; max-width: 100%; width: fit-content;";
113152
reloadButton.onclick = () => window.location.reload();
@@ -118,5 +157,10 @@ load()
118157

119158
const loadingSpinner = document.getElementById("loading_L1");
120159
loadingSpinner?.remove();
160+
// Remove loading message if it exists
161+
const loadingMessage = document.getElementById("loading_message");
162+
if (loadingMessage) {
163+
loadingMessage.remove();
164+
}
121165
document.body.appendChild(errorDiv);
122166
});

0 commit comments

Comments
 (0)