mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 23:55:23 +00:00
fix: retry a plugin bundle that was not ready yet (#1708)
This commit is contained in:
1 parent
95a00d841c
commit
b211311530
3 files changed
+65
-3
No files matched your search
@@ -19,6 +19,7 @@
|
|||||||
- The macOS menu bar icon shows the Termix logo instead of an empty square
|
- The macOS menu bar icon shows the Termix logo instead of an empty square
|
||||||
- Cmd+1 (Alt+1 on Windows and Linux) and the other number shortcuts pick the open tabs as numbered in the tab bar, not the home tab
|
- Cmd+1 (Alt+1 on Windows and Linux) and the other number shortcuts pick the open tabs as numbered in the tab bar, not the home tab
|
||||||
- A plugin the app no longer ships and could not download (for example on a locked down network) shows as failed with the reason on the Plugins tab, instead of as installed
|
- A plugin the app no longer ships and could not download (for example on a locked down network) shows as failed with the reason on the Plugins tab, instead of as installed
|
||||||
|
- The desktop app no longer shows This layout could not be shown when the layout is asked for before its plugin has finished starting; it tries again
|
||||||
|
|
||||||
## 26.10.5
|
## 26.10.5
|
||||||
|
|
||||||
|
|||||||
@@ -85,13 +85,47 @@ function absolute(url: string): string {
|
|||||||
return new URL(url, window.location.href).href;
|
return new URL(url, window.location.href).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IMPORT_RETRY_DELAYS_MS = [500, 1500, 3000];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bundle asked for while its plugin is still starting (a slow desktop boot)
|
||||||
|
* answers 404, and the browser then keeps that failure for the URL. So a
|
||||||
|
* failed import is tried again a few times, each under a new URL.
|
||||||
|
*/
|
||||||
|
export async function importWithRetry<T>(
|
||||||
|
url: string,
|
||||||
|
load: (url: string) => Promise<T>,
|
||||||
|
sleep: (ms: number) => Promise<void> = (ms) =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, ms)),
|
||||||
|
): Promise<T> {
|
||||||
|
for (let attempt = 0; ; attempt++) {
|
||||||
|
const next =
|
||||||
|
attempt === 0
|
||||||
|
? url
|
||||||
|
: `${url}${url.includes("?") ? "&" : "?"}retry=${attempt}`;
|
||||||
|
try {
|
||||||
|
return await load(next);
|
||||||
|
} catch (error) {
|
||||||
|
// A bundle that loaded but threw is broken, not late.
|
||||||
|
const fetchFailed =
|
||||||
|
error instanceof TypeError &&
|
||||||
|
/dynamically imported module|importing a module script/i.test(
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
|
if (!fetchFailed || attempt >= IMPORT_RETRY_DELAYS_MS.length) throw error;
|
||||||
|
await sleep(IMPORT_RETRY_DELAYS_MS[attempt]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const defaultDeps: PluginLoaderDeps = {
|
const defaultDeps: PluginLoaderDeps = {
|
||||||
fetchPlugins: getPlugins,
|
fetchPlugins: getPlugins,
|
||||||
|
|
||||||
async importFrontend(summary) {
|
async importFrontend(summary) {
|
||||||
return (await import(
|
return importWithRetry(
|
||||||
/* @vite-ignore */ absolute(assetUrl(summary, "frontend.js"))
|
absolute(assetUrl(summary, "frontend.js")),
|
||||||
)) as FrontendModule;
|
(url) => import(/* @vite-ignore */ url) as Promise<FrontendModule>,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadLocale(summary, file) {
|
async loadLocale(summary, file) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
startPluginRuntime,
|
startPluginRuntime,
|
||||||
startPreLoginPlugins,
|
startPreLoginPlugins,
|
||||||
syncPlugins,
|
syncPlugins,
|
||||||
|
importWithRetry,
|
||||||
} from "@/plugin-host/loader";
|
} from "@/plugin-host/loader";
|
||||||
import {
|
import {
|
||||||
getPluginRecord,
|
getPluginRecord,
|
||||||
@@ -522,3 +523,29 @@ describe("layouts and themes", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("importWithRetry", () => {
|
||||||
|
const noWait = async () => {};
|
||||||
|
|
||||||
|
it("tries a bundle that failed to load again under a new URL", async () => {
|
||||||
|
const load = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(
|
||||||
|
new TypeError("Failed to fetch dynamically imported module: x"),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce({ ok: true });
|
||||||
|
await expect(
|
||||||
|
importWithRetry("http://h/plugin-assets/a/frontend.js?v=1", load, noWait),
|
||||||
|
).resolves.toEqual({ ok: true });
|
||||||
|
expect(load.mock.calls.map((call) => call[0])).toEqual([
|
||||||
|
"http://h/plugin-assets/a/frontend.js?v=1",
|
||||||
|
"http://h/plugin-assets/a/frontend.js?v=1&retry=1",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not retry a bundle that threw while running", async () => {
|
||||||
|
const load = vi.fn().mockRejectedValue(new Error("boom"));
|
||||||
|
await expect(importWithRetry("u", load, noWait)).rejects.toThrow("boom");
|
||||||
|
expect(load).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user