Compare commits

...

5 Commits

Author SHA1 Message Date
Cursor Agent
a509f73eda chore: exclude test files from TypeScript build 2026-02-09 12:08:16 +00:00
Cursor Agent
bbcb48cb1c test: add tests for webkit messageHandlers polyfill 2026-02-09 12:06:01 +00:00
Cursor Agent
34e04f2c8b fix: add webkit messageHandlers polyfill for Facebook iOS browser
Fixes FORMBRICKS-HJ

- Add defensive polyfill for window.webkit.messageHandlers
- Prevents TypeError when accessing unregistered handlers in Facebook's iOS in-app browser
- Uses Proxy to safely intercept access and return no-op postMessage for missing handlers
- Solves issue with Cal.com embed trying to use native WebKit messaging in unsupported environments
2026-02-09 12:04:55 +00:00
Dhruwang Jariwala
07a6cd6c0e chore: survey ui console warnings (#7228) 2026-02-09 07:39:30 +00:00
Dhruwang Jariwala
335da2f1f5 fix: webhook data not being sent (#7219) 2026-02-09 06:06:30 +00:00
7 changed files with 216 additions and 4 deletions

View File

@@ -141,5 +141,52 @@ describe("Time Utilities", () => {
expect(convertDatesInObject("string")).toBe("string");
expect(convertDatesInObject(123)).toBe(123);
});
test("should not convert dates in contactAttributes", () => {
const input = {
createdAt: "2024-03-20T15:30:00",
contactAttributes: {
createdAt: "2024-03-20T16:30:00",
email: "test@example.com",
},
};
const result = convertDatesInObject(input);
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.contactAttributes.createdAt).toBe("2024-03-20T16:30:00");
expect(result.contactAttributes.email).toBe("test@example.com");
});
test("should not convert dates in variables", () => {
const input = {
updatedAt: "2024-03-20T15:30:00",
variables: {
createdAt: "2024-03-20T16:30:00",
userId: "123",
},
};
const result = convertDatesInObject(input);
expect(result.updatedAt).toBeInstanceOf(Date);
expect(result.variables.createdAt).toBe("2024-03-20T16:30:00");
expect(result.variables.userId).toBe("123");
});
test("should not convert dates in data or meta", () => {
const input = {
createdAt: "2024-03-20T15:30:00",
data: {
createdAt: "2024-03-20T16:30:00",
},
meta: {
updatedAt: "2024-03-20T17:30:00",
},
};
const result = convertDatesInObject(input);
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.data.createdAt).toBe("2024-03-20T16:30:00");
expect(result.meta.updatedAt).toBe("2024-03-20T17:30:00");
});
});
});

View File

@@ -160,7 +160,12 @@ export const convertDatesInObject = <T>(obj: T): T => {
return obj.map((item) => convertDatesInObject(item)) as unknown as T;
}
const newObj: any = {};
const keysToIgnore = new Set(["contactAttributes", "variables", "data", "meta"]);
for (const key in obj) {
if (keysToIgnore.has(key)) {
newObj[key] = obj[key];
continue;
}
if (
(key === "createdAt" || key === "updatedAt") &&
typeof obj[key] === "string" &&

View File

@@ -19,6 +19,8 @@ import tailwindcss from "@tailwindcss/vite";
*/
export default defineConfig({
build: {
// Keep dist when running watch so surveys (and others) can resolve types during parallel go
emptyOutDir: false,
lib: {
entry: "src/index.ts",
formats: ["es"],

View File

@@ -6,6 +6,36 @@ import { FILE_PICK_EVENT } from "@/lib/constants";
import { getI18nLanguage } from "@/lib/i18n-utils";
import { addCustomThemeToDom, addStylesToDom, setStyleNonce } from "@/lib/styles";
// Polyfill for webkit messageHandlers to prevent errors in browsers that don't fully support it
// (e.g., Facebook's iOS in-app browser). This prevents TypeError when accessing unregistered handlers.
if (typeof window !== "undefined") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- WebKit types are not standard
const win = window as any;
if (win.webkit?.messageHandlers) {
const originalMessageHandlers = win.webkit.messageHandlers;
// Create a Proxy that safely handles access to potentially undefined message handlers
win.webkit.messageHandlers = new Proxy(originalMessageHandlers, {
get(target, prop) {
const handler = target[prop as keyof typeof target];
// If the handler doesn't exist, return a safe mock object with a no-op postMessage
if (!handler) {
return {
postMessage: () => {
// Silently ignore - the message handler is not registered in this environment
console.debug(`WebKit message handler "${String(prop)}" is not available in this environment`);
},
};
}
return handler;
},
});
}
}
export const renderSurveyInline = (props: SurveyContainerProps) => {
const inlineProps: SurveyContainerProps = {
...props,

View File

@@ -0,0 +1,126 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
describe("WebKit messageHandlers polyfill", () => {
let originalWebkit: any;
let consoleDebugSpy: any;
beforeEach(() => {
// Save the original webkit object if it exists
originalWebkit = (window as any).webkit;
consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {});
});
afterEach(() => {
// Restore the original webkit object
if (originalWebkit) {
(window as any).webkit = originalWebkit;
} else {
delete (window as any).webkit;
}
consoleDebugSpy.mockRestore();
});
it("should not throw when accessing undefined messageHandlers", () => {
// Setup: Create a webkit object with messageHandlers but no specific handlers
(window as any).webkit = {
messageHandlers: {},
};
// Apply the polyfill logic
const originalMessageHandlers = (window as any).webkit.messageHandlers;
(window as any).webkit.messageHandlers = new Proxy(originalMessageHandlers, {
get(target, prop) {
const handler = target[prop as keyof typeof target];
if (!handler) {
return {
postMessage: () => {
console.debug(`WebKit message handler "${String(prop)}" is not available in this environment`);
},
};
}
return handler;
},
});
// Test: Accessing an undefined handler should not throw
expect(() => {
(window as any).webkit.messageHandlers.undefinedHandler.postMessage("test");
}).not.toThrow();
// Verify console.debug was called
expect(consoleDebugSpy).toHaveBeenCalledWith(
'WebKit message handler "undefinedHandler" is not available in this environment'
);
});
it("should still work with existing handlers", () => {
// Setup: Create a webkit object with a real handler
const mockPostMessage = vi.fn();
(window as any).webkit = {
messageHandlers: {
existingHandler: {
postMessage: mockPostMessage,
},
},
};
// Apply the polyfill logic
const originalMessageHandlers = (window as any).webkit.messageHandlers;
(window as any).webkit.messageHandlers = new Proxy(originalMessageHandlers, {
get(target, prop) {
const handler = target[prop as keyof typeof target];
if (!handler) {
return {
postMessage: () => {
console.debug(`WebKit message handler "${String(prop)}" is not available in this environment`);
},
};
}
return handler;
},
});
// Test: Existing handler should still work
(window as any).webkit.messageHandlers.existingHandler.postMessage("test message");
expect(mockPostMessage).toHaveBeenCalledWith("test message");
});
it("should handle multiple undefined handlers", () => {
// Setup
(window as any).webkit = {
messageHandlers: {},
};
// Apply the polyfill logic
const originalMessageHandlers = (window as any).webkit.messageHandlers;
(window as any).webkit.messageHandlers = new Proxy(originalMessageHandlers, {
get(target, prop) {
const handler = target[prop as keyof typeof target];
if (!handler) {
return {
postMessage: () => {
console.debug(`WebKit message handler "${String(prop)}" is not available in this environment`);
},
};
}
return handler;
},
});
// Test: Multiple undefined handlers should not throw
expect(() => {
(window as any).webkit.messageHandlers.handler1.postMessage("test1");
(window as any).webkit.messageHandlers.handler2.postMessage("test2");
(window as any).webkit.messageHandlers.handler3.postMessage("test3");
}).not.toThrow();
expect(consoleDebugSpy).toHaveBeenCalledTimes(3);
});
});

View File

@@ -7,10 +7,12 @@
"jsx": "react-jsx",
"jsxImportSource": "preact",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"@formbricks/survey-ui": ["../survey-ui"]
},
"resolveJsonModule": true
},
"exclude": ["**/*.test.ts", "**/*.test.tsx"],
"extends": "@formbricks/config-typescript/js-library.json",
"include": ["src", "../types/surveys.d.ts"]
}

View File

@@ -88,16 +88,16 @@
"persistent": true
},
"@formbricks/surveys#build": {
"dependsOn": ["^build"],
"dependsOn": ["^build", "@formbricks/survey-ui#build"],
"outputs": ["dist/**"]
},
"@formbricks/surveys#build:dev": {
"dependsOn": ["^build:dev", "@formbricks/i18n-utils#build"],
"dependsOn": ["^build:dev", "@formbricks/i18n-utils#build", "@formbricks/survey-ui#build:dev"],
"outputs": ["dist/**"]
},
"@formbricks/surveys#go": {
"cache": false,
"dependsOn": ["@formbricks/surveys#build"],
"dependsOn": ["@formbricks/survey-ui#build", "@formbricks/surveys#build"],
"persistent": true
},
"@formbricks/surveys#test": {