Added retry support to the HtmlIsland
This commit is contained in:
parent
0e20accee6
commit
7d0a7514ad
@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useCallback } from "react";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Namespaces } from "../../i18n/i18n";
|
import { Namespaces } from "../../i18n/i18n";
|
||||||
|
import ErrorBlock from "./ErrorBlock";
|
||||||
|
|
||||||
export interface HtmlIslandProps {
|
export interface HtmlIslandProps {
|
||||||
url: string;
|
url: string;
|
||||||
@ -20,29 +21,62 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
|
|||||||
const { t: tIsland } = useTranslation(Namespaces.HtmlIsland);
|
const { t: tIsland } = useTranslation(Namespaces.HtmlIsland);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Load the island HTML
|
// Retry-capable fetch helper
|
||||||
//
|
//
|
||||||
const loadIsland = useCallback(() => {
|
const fetchWithRetry = useCallback(
|
||||||
setError(null);
|
async (
|
||||||
|
attempt: number,
|
||||||
fetch(url, { credentials: "include" })
|
maxAttempts: number,
|
||||||
.then((r) => {
|
delayMs: number,
|
||||||
|
): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const r = await fetch(url, { credentials: "include" });
|
||||||
if (!r.ok) throw new Error(`Failed to load island: ${r.status}`);
|
if (!r.ok) throw new Error(`Failed to load island: ${r.status}`);
|
||||||
return r.text();
|
return await r.text();
|
||||||
})
|
} catch (err) {
|
||||||
.then((text) => setHtml(text))
|
if (attempt >= maxAttempts) {
|
||||||
.catch(() => {
|
throw err;
|
||||||
setError(tIsland("island.loadError"));
|
}
|
||||||
toast.error(tIsland("island.loadError"));
|
|
||||||
});
|
const jitter = Math.random() * 150;
|
||||||
}, [url, tIsland]);
|
const nextDelay = delayMs * Math.pow(2, attempt) + jitter;
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, nextDelay));
|
||||||
|
|
||||||
|
return fetchWithRetry(attempt + 1, maxAttempts, delayMs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[url],
|
||||||
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Initial load + reload when URL changes
|
// Initial load + reload when URL changes (with retry + cancellation)
|
||||||
//
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadIsland();
|
let cancelled = false;
|
||||||
}, [loadIsland]);
|
|
||||||
|
const load = async () => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const text = await fetchWithRetry(0, 4, 300);
|
||||||
|
if (!cancelled) {
|
||||||
|
setHtml(text);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) {
|
||||||
|
setError(tIsland("island.loadError"));
|
||||||
|
toast.error(tIsland("island.loadError"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
load();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [url, tIsland, fetchWithRetry]);
|
||||||
|
|
||||||
//
|
//
|
||||||
// After HTML is injected, run scripts + bind form handling
|
// After HTML is injected, run scripts + bind form handling
|
||||||
@ -98,8 +132,7 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
|
|||||||
form.getAttribute("data-full-page-redirect") === "true";
|
form.getAttribute("data-full-page-redirect") === "true";
|
||||||
|
|
||||||
if (shouldFullPageRedirect) {
|
if (shouldFullPageRedirect) {
|
||||||
// Do NOT intercept this form – let the browser handle POST + redirect
|
return; // allow normal POST + redirect
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitHandler = async (e: Event) => {
|
const submitHandler = async (e: Event) => {
|
||||||
@ -131,7 +164,14 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
|
|||||||
toast.success(
|
toast.success(
|
||||||
successMessage ? successMessage : tIsland("island.saveSuccess"),
|
successMessage ? successMessage : tIsland("island.saveSuccess"),
|
||||||
);
|
);
|
||||||
loadIsland();
|
// reload island
|
||||||
|
try {
|
||||||
|
const text = await fetchWithRetry(0, 4, 300);
|
||||||
|
setHtml(text);
|
||||||
|
} catch {
|
||||||
|
setError(tIsland("island.loadError"));
|
||||||
|
toast.error(tIsland("island.loadError"));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,28 +190,14 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
|
|||||||
|
|
||||||
form.addEventListener("submit", submitHandler);
|
form.addEventListener("submit", submitHandler);
|
||||||
|
|
||||||
// Cleanup on re-render
|
|
||||||
return () => {
|
return () => {
|
||||||
form.removeEventListener("submit", submitHandler);
|
form.removeEventListener("submit", submitHandler);
|
||||||
};
|
};
|
||||||
}, [html, islandId, successMessage, tIsland, loadIsland]);
|
}, [html, islandId, successMessage, tIsland, fetchWithRetry]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{error && (
|
<ErrorBlock error={error ?? undefined} />
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: "#fee",
|
|
||||||
border: "1px solid #c00",
|
|
||||||
padding: "8px",
|
|
||||||
marginBottom: "10px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
color: "#900",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div ref={containerRef} dangerouslySetInnerHTML={{ __html: html }} />
|
<div ref={containerRef} dangerouslySetInnerHTML={{ __html: html }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user