Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ Replace your-workspace-id with your actual workspace ID. You can find your works

> `environmentId` is still accepted for backward compatibility but is deprecated and will be removed in a future version. Prefer `workspaceId`.

### Renderer prefetching and Content Security Policy

The SDK prefetches the survey renderer by default. If your host application's Content Security
Policy blocks these prefetch requests, disable them during setup:

```javascript
formbricks.setup({
workspaceId: "your-workspace-id",
appUrl: "https://app.formbricks.com",
prefetch: false,
});
```

The renderer still loads when a survey is triggered. Omitting `prefetch` preserves the default;
`prefetch: true` explicitly enables prefetching. This requires a Formbricks instance serving
js-core with `prefetch` support. Older instances ignore the option, so upgrading this npm package
alone does not disable their prefetch requests.

### Dark mode

Surveys render light by default. Pass `appearance` to `setup()`, or change it at any time with
Expand Down
18 changes: 18 additions & 0 deletions packages/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ Replace your-workspace-id with your actual workspace ID. You can find your works

> `environmentId` is still accepted for backward compatibility but is deprecated and will be removed in a future version. Prefer `workspaceId`.

### Renderer prefetching and Content Security Policy

The SDK prefetches the survey renderer by default. If your host application's Content Security
Policy blocks these prefetch requests, disable them during setup:

```javascript
formbricks.setup({
workspaceId: "your-workspace-id",
appUrl: "https://app.formbricks.com",
prefetch: false,
});
```

The renderer still loads when a survey is triggered. Omitting `prefetch` preserves the default;
`prefetch: true` explicitly enables prefetching. This requires a Formbricks instance serving
js-core with `prefetch` support. Older instances ignore the option, so upgrading this npm package
alone does not disable their prefetch requests.

### Dark mode

Surveys render light by default. Pass `appearance` to `setup()`, or change it at any time with
Expand Down
16 changes: 16 additions & 0 deletions packages/js/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,22 @@ describe("type safety", () => {
const testTypeSafety = () => {
void formbricks.setup({ workspaceId: "ws", appUrl: "url" });
void formbricks.setup({ environmentId: "env", appUrl: "url" });
void formbricks.setup({
workspaceId: "ws",
appUrl: "url",
prefetch: false,
});
void formbricks.setup({
environmentId: "env",
appUrl: "url",
prefetch: true,
});
Comment thread
mattinannt marked this conversation as resolved.
Resolved
void formbricks.setup({
workspaceId: "ws",
appUrl: "url",
// @ts-expect-error -- Prefetch accepts booleans, not string values.
prefetch: "false",
});
void formbricks.track("event");
void formbricks.setEmail("email");
void formbricks.setAttribute("key", "value");
Expand Down
50 changes: 50 additions & 0 deletions packages/js/src/lib/load-formbricks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,33 @@ describe("load-formbricks", () => {

describe("setup", () => {
describe("setup functionality", () => {
test.each([
{ workspaceId: "ws123" },
{ environmentId: "env123" },
{ workspaceId: "ws123", environmentId: "env123" },
])("should preserve prefetch for setup with %j", async (identifier) => {
typedGlobalThis.formbricks = mockFormbricks;
Comment thread
mattinannt marked this conversation as resolved.
Resolved

// Each call must reach the loaded core with the exact caller preference,
// including false; an omitted preference leaves the core's default intact.
for (const prefetch of [false, true, undefined]) {
const config = {
...identifier,
appUrl: "https://app.formbricks.com/",
prefetch,
};

await setup(config);

const forwarded = mockFormbricks.setup.mock.lastCall?.[0];
expect(forwarded).toStrictEqual({
...identifier,
appUrl: "https://app.formbricks.com",
...(prefetch === undefined ? {} : { prefetch }),
});
}
});

test("should handle setup call with valid arguments", async () => {
const setupArgs = {
appUrl: "https://app.formbricks.com",
Expand All @@ -162,6 +189,29 @@ describe("load-formbricks", () => {
expect(mockFormbricks.setup).toHaveBeenCalledWith(setupArgs);
});

test("should preserve prefetch false when loading the core script", async () => {
const setupArgs = {
appUrl: "https://app.formbricks.com",
workspaceId: "ws123",
prefetch: false,
};

const mockAppendChild = vi
.spyOn(document.head, "appendChild")
.mockImplementation(createSuccessfulScriptMock());

await setup(setupArgs);

expect(mockAppendChild).toHaveBeenCalledWith(
expect.objectContaining({
src: `${setupArgs.appUrl}/js/formbricks.umd.cjs`,
type: "text/javascript",
async: true,
}),
);
expect(mockFormbricks.setup).toHaveBeenCalledExactlyOnceWith(setupArgs);
});

test("should handle setup call with a trailing slash in the appUrl", async () => {
const validAppUrl = "https://app.formbricks.com";
const invalidAppUrl = "https://app.formbricks.com/";
Expand Down
5 changes: 4 additions & 1 deletion packages/js/src/lib/load-formbricks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const loadFormbricksSDK = async (appUrl: string): Promise<Result<void>> => {
};

const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => {
const { appUrl, environmentId, workspaceId, appearance } = config;
const { appUrl, environmentId, workspaceId, appearance, prefetch } = config;

if (!appUrl) {
console.error("🧱 Formbricks - Error: appUrl is required");
Expand All @@ -202,20 +202,23 @@ const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => {

// Only forwarded when set, so setup() calls without it reach the SDK exactly as before.
const appearanceArg = appearance === undefined ? {} : { appearance };
const prefetchArg = prefetch === undefined ? {} : { prefetch };

if (workspaceId) {
return {
appUrl: appUrlWithoutTrailingSlash,
workspaceId,
...(environmentId ? { environmentId } : {}),
...appearanceArg,
...prefetchArg,
};
}

return {
appUrl: appUrlWithoutTrailingSlash,
environmentId: environmentId as string,
...appearanceArg,
...prefetchArg,
};
};

Expand Down
6 changes: 6 additions & 0 deletions packages/js/src/types/formbricks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,10 @@ export type TSetupConfig = (
* Formbricks 6.1 or later; older instances ignore it.
*/
appearance?: TAppearance;
/**
* Whether to prefetch the survey renderer. Defaults to true. Set to false when a strict CSP
* blocks prefetch requests; the renderer still loads when a survey is triggered. Requires a
* Formbricks instance with js-core prefetch support; older instances ignore this option.
*/
prefetch?: boolean;
};
Loading