Skip to content

Commit c0d4dca

Browse files
committed
fix: address CodeRabbit review findings
1 parent f6f4e37 commit c0d4dca

7 files changed

Lines changed: 116 additions & 34 deletions

File tree

packages/react-native/src/components/survey-web-view.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,20 @@ const renderHtml = (
326326
): string => {
327327
const surveyScriptUrl = getSurveyScriptUrl(options.appUrl);
328328

329+
if (!surveyScriptUrl) {
330+
return `
331+
<!doctype html>
332+
<html>
333+
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0">
334+
<head>
335+
<title>Formbricks WebView Survey</title>
336+
</head>
337+
<body style="overflow: hidden; height: 100vh; margin: 0;">
338+
</body>
339+
</html>
340+
`;
341+
}
342+
329343
return `
330344
<!doctype html>
331345
<html>

packages/react-native/src/components/tests/survey-script-url.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { describe, expect, test } from "vitest";
22
import { getSurveyScriptUrl } from "@/components/utils/survey-script-url";
33

44
describe("getSurveyScriptUrl()", () => {
5-
test("builds the default local development script URL", () => {
6-
expect(getSurveyScriptUrl()).toBe("http://localhost:3000/js/surveys.umd.cjs");
5+
test("returns null when appUrl is missing", () => {
6+
expect(getSurveyScriptUrl()).toBeNull();
77
});
88

99
test("builds the script URL from a root-hosted appUrl", () => {
@@ -30,9 +30,11 @@ describe("getSurveyScriptUrl()", () => {
3030
);
3131
});
3232

33-
test("rejects non-http protocols", () => {
34-
expect(() => getSurveyScriptUrl("file:///tmp/formbricks")).toThrow(
35-
"Formbricks appUrl must use http or https",
36-
);
33+
test("returns null for non-http protocols", () => {
34+
expect(getSurveyScriptUrl("file:///tmp/formbricks")).toBeNull();
35+
});
36+
37+
test("returns null for invalid URLs", () => {
38+
expect(getSurveyScriptUrl("not-a-url")).toBeNull();
3739
});
3840
});
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
export const getSurveyScriptUrl = (appUrl?: string): string => {
2-
const url = new URL(appUrl ?? "http://localhost:3000");
3-
4-
if (url.protocol !== "http:" && url.protocol !== "https:") {
5-
throw new Error("Formbricks appUrl must use http or https");
1+
export const getSurveyScriptUrl = (appUrl?: string): string | null => {
2+
if (!appUrl) {
3+
return null;
64
}
75

8-
const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
9-
url.pathname = `${basePath}js/surveys.umd.cjs`;
10-
url.search = "";
11-
url.hash = "";
6+
try {
7+
const url = new URL(appUrl);
8+
9+
if (url.protocol !== "http:" && url.protocol !== "https:") {
10+
return null;
11+
}
1212

13-
return url.toString();
13+
const basePath = url.pathname.endsWith("/")
14+
? url.pathname
15+
: `${url.pathname}/`;
16+
url.pathname = `${basePath}js/surveys.umd.cjs`;
17+
url.search = "";
18+
url.hash = "";
19+
20+
return url.toString();
21+
} catch {
22+
return null;
23+
}
1424
};

packages/react-native/src/lib/common/command-queue.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
/* eslint-disable no-console -- we need to log global errors */
22
import { checkSetup } from "@/lib/common/setup";
33
import { wrapThrowsAsync } from "@/lib/common/utils";
4-
import type { Result } from "@/types/error";
4+
import { okVoid, type Result } from "@/types/error";
5+
6+
type QueueCommandResult = Result<void, unknown> | void;
7+
type QueueCommand = (
8+
...args: unknown[]
9+
) => Promise<QueueCommandResult> | QueueCommandResult;
510

611
export class CommandQueue {
712
private readonly queue: {
8-
command: (
9-
...args: unknown[]
10-
) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>;
13+
command: QueueCommand;
1114
checkSetup: boolean;
1215
commandArgs: unknown[];
1316
}[] = [];
@@ -16,19 +19,12 @@ export class CommandQueue {
1619
private commandPromise: Promise<void> | null = null;
1720

1821
public add<A extends unknown[]>(
19-
command: (
20-
...args: A
21-
) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>,
22+
command: (...args: A) => Promise<QueueCommandResult> | QueueCommandResult,
2223
shouldCheckSetup = true,
2324
...args: A
2425
): void {
2526
this.queue.push({
26-
command: command as (
27-
...commandArgs: unknown[]
28-
) =>
29-
| Promise<Result<void, unknown>>
30-
| Result<void, unknown>
31-
| Promise<void>,
27+
command: command as QueueCommand,
3228
checkSetup: shouldCheckSetup,
3329
commandArgs: args,
3430
});
@@ -65,10 +61,9 @@ export class CommandQueue {
6561
}
6662

6763
const executeCommand = async (): Promise<Result<void, unknown>> =>
68-
(await currentItem.command.apply(
69-
null,
70-
currentItem.commandArgs,
71-
)) as Result<void, unknown>;
64+
normalizeQueueCommandResult(
65+
await currentItem.command.apply(null, currentItem.commandArgs),
66+
);
7267

7368
const result = await wrapThrowsAsync(executeCommand)();
7469

@@ -86,3 +81,17 @@ export class CommandQueue {
8681
}
8782
}
8883
}
84+
85+
const normalizeQueueCommandResult = (
86+
value: QueueCommandResult,
87+
): Result<void, unknown> => {
88+
if (!value) {
89+
return okVoid();
90+
}
91+
92+
if ("ok" in value && typeof value.ok === "boolean") {
93+
return value;
94+
}
95+
96+
return okVoid();
97+
};

packages/react-native/src/lib/common/tests/command-queue.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ describe("CommandQueue", () => {
8686
expect(cmd).toHaveBeenCalledTimes(1);
8787
});
8888

89+
test("treats Promise<void> commands as successful", async () => {
90+
const consoleErrorSpy = vi
91+
.spyOn(console, "error")
92+
.mockImplementation(() => undefined);
93+
const cmd = vi.fn(async (): Promise<void> => {
94+
await delayedResult(undefined, 10);
95+
});
96+
97+
vi.mocked(checkSetup).mockReturnValue({ ok: true, data: undefined });
98+
99+
queue.add(cmd, true);
100+
await queue.wait();
101+
102+
expect(cmd).toHaveBeenCalledTimes(1);
103+
expect(consoleErrorSpy).not.toHaveBeenCalled();
104+
consoleErrorSpy.mockRestore();
105+
});
106+
89107
test("logs errors if a command throws or returns error", async () => {
90108
// Spy on console.error to see if it's called
91109
const consoleErrorSpy = vi
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, expect, test } from "vitest";
2+
import { ZResponseData, ZResponseUpdate } from "@/types/response";
3+
4+
describe("response schemas", () => {
5+
test("accepts nested string records in response data", () => {
6+
const result = ZResponseData.safeParse({
7+
simple: "value",
8+
nested: {
9+
key: "value",
10+
},
11+
});
12+
13+
expect(result.success).toBe(true);
14+
});
15+
16+
test("accepts nested string records in response updates", () => {
17+
const result = ZResponseUpdate.safeParse({
18+
finished: true,
19+
data: {
20+
nested: {
21+
key: "value",
22+
},
23+
},
24+
});
25+
26+
expect(result.success).toBe(true);
27+
});
28+
});

packages/react-native/src/types/response.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export interface TResponseUpdate {
2626
endingId?: string | null;
2727
}
2828

29+
const ZNestedResponseData = z.record(z.string(), z.string());
2930
export const ZResponseData = z.record(
3031
z.string(),
31-
z.union([z.string(), z.number(), z.array(z.string())]),
32+
z.union([z.string(), z.number(), z.array(z.string()), ZNestedResponseData]),
3233
);
3334
export const ZResponseVariables = z.record(
3435
z.string(),

0 commit comments

Comments
 (0)