Skip to content

Commit 7eafe6b

Browse files
committed
Add timeout to connected files lookup
1 parent 2e8f098 commit 7eafe6b

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

packages/webviewer/src/fm-bridge.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { HtmlTagDescriptor, Plugin } from "vite";
22

33
const TRAILING_SLASH_PATTERN = /\/$/;
4+
const CONNECTED_FILES_TIMEOUT_MS = 5000;
45

56
export interface FmBridgeOptions {
67
fileName?: string;
@@ -42,14 +43,23 @@ export const resolveWsUrl = (options: Pick<FmBridgeOptions, "fmHttpBaseUrl" | "w
4243

4344
export const discoverConnectedFileName = async (baseUrl: string): Promise<string> => {
4445
const connectedFilesUrl = `${normalizeBaseUrl(baseUrl)}/connectedFiles`;
46+
const controller = new AbortController();
47+
const timeoutId = setTimeout(() => {
48+
controller.abort();
49+
}, CONNECTED_FILES_TIMEOUT_MS);
50+
const reachabilityErrorMessage = `fmBridge could not reach ${connectedFilesUrl}. Start fm-http and connect a FileMaker webviewer.`;
4551

4652
let response: Response;
4753
try {
48-
response = await fetch(connectedFilesUrl);
54+
response = await fetch(connectedFilesUrl, {
55+
signal: controller.signal,
56+
});
4957
} catch (error) {
50-
throw new Error(`fmBridge could not reach ${connectedFilesUrl}. Start fm-http and connect a FileMaker webviewer.`, {
58+
throw new Error(reachabilityErrorMessage, {
5159
cause: error,
5260
});
61+
} finally {
62+
clearTimeout(timeoutId);
5363
}
5464

5565
if (!response.ok) {

packages/webviewer/tests/vite-plugins.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,38 @@ describe("discoverConnectedFileName", () => {
7575
);
7676

7777
await expect(discoverConnectedFileName("http://localhost:1365")).resolves.toBe("Contacts");
78-
expect(globalThis.fetch).toHaveBeenCalledWith("http://localhost:1365/connectedFiles");
78+
expect(globalThis.fetch).toHaveBeenCalledWith(
79+
"http://localhost:1365/connectedFiles",
80+
expect.objectContaining({ signal: expect.any(AbortSignal) }),
81+
);
82+
});
83+
84+
it("aborts stalled requests with the standard reachability error", async () => {
85+
vi.useFakeTimers();
86+
87+
try {
88+
const abortError = new DOMException("The operation was aborted.", "AbortError");
89+
vi.mocked(globalThis.fetch).mockImplementation(
90+
(_input, init) =>
91+
new Promise((_resolve, reject) => {
92+
init?.signal?.addEventListener("abort", () => {
93+
reject(abortError);
94+
});
95+
}),
96+
);
97+
98+
const pendingRequest = discoverConnectedFileName("http://localhost:1365");
99+
const pendingExpectation = expect(pendingRequest).rejects.toMatchObject({
100+
cause: abortError,
101+
message:
102+
"fmBridge could not reach http://localhost:1365/connectedFiles. Start fm-http and connect a FileMaker webviewer.",
103+
});
104+
105+
await vi.advanceTimersByTimeAsync(5000);
106+
await pendingExpectation;
107+
} finally {
108+
vi.useRealTimers();
109+
}
79110
});
80111

81112
it("rejects non-ok HTTP responses", async () => {

0 commit comments

Comments
 (0)