diff --git a/apps/docs/app/self-hosting/migration-guide/page.mdx b/apps/docs/app/self-hosting/migration-guide/page.mdx
index 53e6473e14..1bcbcb0244 100644
--- a/apps/docs/app/self-hosting/migration-guide/page.mdx
+++ b/apps/docs/app/self-hosting/migration-guide/page.mdx
@@ -20,7 +20,9 @@ Formbricks v2.0 comes with huge features such as Multi-Language Surveys and Adva
and
- If you've used the Formbricks Enterprise Edition with a free beta license key, your instance will be downgraded to the Community Edition 2.0. You find all license details on the [license page.](/self-hosting/license/)
+ If you've used the Formbricks Enterprise Edition with a free beta license key, your instance will be
+ downgraded to the Community Edition 2.0. You find all license details on the [license
+ page.](/self-hosting/license/)
### Steps to Migrate
@@ -35,7 +37,7 @@ To run all these steps, please navigate to the `formbricks` folder where your `d
```bash
-docker exec formbricks-quickstart-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.0_$(date +%Y%m%d_%H%M%S).dump
+docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.0_$(date +%Y%m%d_%H%M%S).dump
```
@@ -51,7 +53,19 @@ docker exec formbricks-quickstart-postgres-1 pg_dump -Fc -U postgres -d formbric
restore scenario you will need to use `psql` then with an empty `formbricks` database.
-2. Stop the running Formbricks instance & remove the related containers:
+2. Pull the latest version of Formbricks:
+
+
+
+
+```bash
+docker-compose pull
+```
+
+
+
+
+3. Stop the running Formbricks instance & remove the related containers:
@@ -63,7 +77,7 @@ docker-compose down
-3. Restarting the containers will automatically pull the latest version of Formbricks:
+4. Restarting the containers with the latest version of Formbricks:
@@ -75,7 +89,7 @@ docker-compose up -d
-4. Now let's migrate the data to the latest schema:
+5. Now let's migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using `docker network ls`
@@ -83,6 +97,7 @@ docker-compose up -d
```bash
+docker pull ghcr.io/formbricks/data-migrations:latest && \
docker run --rm \
--network=formbricks_default \
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
@@ -95,7 +110,7 @@ docker run --rm \
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
-5. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
+6. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
### App Surveys with @formbricks/js
diff --git a/apps/web/app/(app)/components/FormbricksClient.tsx b/apps/web/app/(app)/components/FormbricksClient.tsx
index 18832a0712..e516e7cee3 100644
--- a/apps/web/app/(app)/components/FormbricksClient.tsx
+++ b/apps/web/app/(app)/components/FormbricksClient.tsx
@@ -2,7 +2,7 @@
import { formbricksEnabled } from "@/app/lib/formbricks";
import { usePathname, useSearchParams } from "next/navigation";
-import { useEffect } from "react";
+import { useCallback, useEffect } from "react";
import formbricks from "@formbricks/js/app";
import { env } from "@formbricks/lib/env";
@@ -15,22 +15,23 @@ export const FormbricksClient = ({ session }) => {
const pathname = usePathname();
const searchParams = useSearchParams();
- useEffect(() => {
- if (formbricksEnabled && session?.user && formbricks) {
- formbricks.init({
- environmentId: env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
- apiHost: env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
- userId: session.user.id,
- });
- formbricks.setEmail(session.user.email);
- }
- }, [session]);
+ const initializeFormbricksAndSetupRouteChanges = useCallback(async () => {
+ formbricks.init({
+ environmentId: env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
+ apiHost: env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
+ userId: session.user.id,
+ });
+ formbricks.setEmail(session.user.email);
+
+ formbricks.registerRouteChange();
+ }, [session.user.email, session.user.id]);
useEffect(() => {
- if (formbricksEnabled && formbricks) {
- formbricks?.registerRouteChange();
+ if (formbricksEnabled && session?.user && formbricks) {
+ initializeFormbricksAndSetupRouteChanges();
}
- }, [pathname, searchParams]);
+ }, [session, pathname, searchParams, initializeFormbricksAndSetupRouteChanges]);
+
return null;
};
diff --git a/apps/web/app/api/v1/(legacy)/client/responses/route.ts b/apps/web/app/api/v1/(legacy)/client/responses/route.ts
index d4e55962be..810ffca342 100644
--- a/apps/web/app/api/v1/(legacy)/client/responses/route.ts
+++ b/apps/web/app/api/v1/(legacy)/client/responses/route.ts
@@ -59,7 +59,7 @@ export const POST = async (request: Request): Promise => {
url: responseInput?.meta?.url,
userAgent: {
browser: agent?.browser.name,
- device: agent?.device.type,
+ device: agent?.device.type || "desktop",
os: agent?.os.name,
},
country: country,
diff --git a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
index df724a99a5..2f93f8e398 100644
--- a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
+++ b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
@@ -82,7 +82,7 @@ export const POST = async (request: Request, context: Context): Promise actionClass.type === "noCode");
// Define 'transformedSurveys' which can be an array of either TLegacySurvey or TSurvey.
- let transformedSurveys: TLegacySurvey[] | TSurvey[] = surveys;
+ let transformedSurveys: TLegacySurvey[] | TSurvey[] = filteredSurveys;
let state: TJsWebsiteStateSync | TJsWebsiteLegacyStateSync = {
surveys: !isInAppSurveyLimitReached ? transformedSurveys : [],
actionClasses,
diff --git a/packages/js/package.json b/packages/js/package.json
index e6f2319f93..7b5766f614 100644
--- a/packages/js/package.json
+++ b/packages/js/package.json
@@ -1,7 +1,7 @@
{
"name": "@formbricks/js",
"license": "MIT",
- "version": "2.0.0",
+ "version": "2.0.1",
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
"homepage": "https://formbricks.com",
"repository": {
diff --git a/packages/js/src/app.ts b/packages/js/src/app.ts
index 9bd96d69b7..131f87619d 100644
--- a/packages/js/src/app.ts
+++ b/packages/js/src/app.ts
@@ -1,7 +1,7 @@
import { TFormbricksApp } from "@formbricks/js-core/app";
import { TFormbricksWebsite } from "@formbricks/js-core/website";
-import { Result, wrapThrowsAsync } from "../../types/errorHandlers";
+import { loadFormbricksToProxy } from "./shared/loadFormbricks";
declare global {
interface Window {
@@ -9,97 +9,9 @@ declare global {
}
}
-// load the sdk, return the result
-const loadFormbricksAppSDK = async (apiHost: string): Promise> => {
- if (!window.formbricks) {
- const res = await fetch(`${apiHost}/api/packages/app`);
-
- // failed to fetch the app package
- if (!res.ok) {
- return { ok: false, error: new Error("Failed to load Formbricks App SDK") };
- }
-
- const sdkScript = await res.text();
- const scriptTag = document.createElement("script");
- scriptTag.innerHTML = sdkScript;
- document.head.appendChild(scriptTag);
-
- const getFormbricks = async () =>
- new Promise((resolve, reject) => {
- const checkInterval = setInterval(() => {
- if (window.formbricks) {
- clearInterval(checkInterval);
- resolve();
- }
- }, 100);
-
- setTimeout(() => {
- clearInterval(checkInterval);
- reject(new Error("Formbricks App SDK loading timed out"));
- }, 10000);
- });
-
- try {
- await getFormbricks();
- return { ok: true, data: undefined };
- } catch (error: any) {
- // formbricks loading failed, return the error
- return {
- ok: false,
- error: new Error(error.message ?? "Failed to load Formbricks App SDK"),
- };
- }
- }
-
- return { ok: true, data: undefined };
-};
-
-type FormbricksAppMethods = {
- [K in keyof TFormbricksApp]: TFormbricksApp[K] extends Function ? K : never;
-}[keyof TFormbricksApp];
-
const formbricksProxyHandler: ProxyHandler = {
get(_target, prop, _receiver) {
- return async (...args: any[]) => {
- if (!window.formbricks) {
- if (prop !== "init") {
- console.error(
- "🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
- );
- return;
- }
-
- // still need to check if the apiHost is passed
- if (!args[0]) {
- console.error("🧱 Formbricks - Global error: You need to pass the apiHost as the first argument");
- return;
- }
-
- const { apiHost } = args[0];
- const loadSDKResult = await wrapThrowsAsync(loadFormbricksAppSDK)(apiHost);
-
- if (!loadSDKResult.ok) {
- console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
- return;
- }
- }
-
- // @ts-expect-error
- if (window.formbricks && typeof window.formbricks[prop as FormbricksAppMethods] !== "function") {
- console.error(
- `🧱 Formbricks - Global error: Formbricks App SDK does not support method ${String(prop)}`
- );
- return;
- }
-
- try {
- // @ts-expect-error
- return (window.formbricks[prop as FormbricksAppMethods] as Function)(...args);
- } catch (error) {
- console.error(`Something went wrong: ${error}`);
- return;
- }
- };
+ return (...args: any[]) => loadFormbricksToProxy(prop as string, "app", ...args);
},
};
diff --git a/packages/js/src/index.ts b/packages/js/src/index.ts
deleted file mode 100644
index 2c8b92f2d2..0000000000
--- a/packages/js/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as formbricksApp } from "./app";
-export { default as formbricksWebsite } from "./website";
diff --git a/packages/js/src/methodQueue.ts b/packages/js/src/methodQueue.ts
new file mode 100644
index 0000000000..5df9102ebc
--- /dev/null
+++ b/packages/js/src/methodQueue.ts
@@ -0,0 +1,38 @@
+// Simple queue for formbricks methods
+
+export class MethodQueue {
+ private queue: (() => Promise)[] = [];
+ private isExecuting = false;
+
+ add = (method: () => Promise) => {
+ this.queue.push(method);
+ this.run();
+ };
+
+ private runNext = async () => {
+ if (this.isExecuting) return;
+
+ const method = this.queue.shift();
+ if (method) {
+ this.isExecuting = true;
+ try {
+ await method();
+ } finally {
+ this.isExecuting = false;
+ if (this.queue.length > 0) {
+ this.runNext();
+ }
+ }
+ }
+ };
+
+ run = async () => {
+ if (!this.isExecuting && this.queue.length > 0) {
+ await this.runNext();
+ }
+ };
+
+ clear = () => {
+ this.queue = [];
+ };
+}
diff --git a/packages/js/src/shared/loadFormbricks.ts b/packages/js/src/shared/loadFormbricks.ts
new file mode 100644
index 0000000000..b9250a8584
--- /dev/null
+++ b/packages/js/src/shared/loadFormbricks.ts
@@ -0,0 +1,122 @@
+import { Result, wrapThrowsAsync } from "../../../types/errorHandlers";
+import { MethodQueue } from "../methodQueue";
+
+let isInitializing = false;
+let isInitialized = false;
+const methodQueue = new MethodQueue();
+
+// Load the SDK, return the result
+const loadFormbricksSDK = async (apiHost: string, sdkType: "app" | "website"): Promise> => {
+ if (!window.formbricks) {
+ const res = await fetch(`${apiHost}/api/packages/${sdkType}`);
+
+ // Failed to fetch the app package
+ if (!res.ok) {
+ return { ok: false, error: new Error(`Failed to load Formbricks ${sdkType} SDK`) };
+ }
+
+ const sdkScript = await res.text();
+ const scriptTag = document.createElement("script");
+ scriptTag.innerHTML = sdkScript;
+ document.head.appendChild(scriptTag);
+
+ const getFormbricks = async () =>
+ new Promise((resolve, reject) => {
+ const checkInterval = setInterval(() => {
+ if (window.formbricks) {
+ clearInterval(checkInterval);
+ resolve();
+ }
+ }, 100);
+
+ setTimeout(() => {
+ clearInterval(checkInterval);
+ reject(new Error(`Formbricks ${sdkType} SDK loading timed out`));
+ }, 10000);
+ });
+
+ try {
+ await getFormbricks();
+ return { ok: true, data: undefined };
+ } catch (error: any) {
+ return {
+ ok: false,
+ error: new Error(error.message ?? `Failed to load Formbricks ${sdkType} SDK`),
+ };
+ }
+ }
+
+ return { ok: true, data: undefined };
+};
+
+// TODO: @pandeymangg - Fix these types
+// type FormbricksAppMethods = {
+// [K in keyof TFormbricksApp]: TFormbricksApp[K] extends Function ? K : never;
+// }[keyof TFormbricksApp];
+
+// type FormbricksWebsiteMethods = {
+// [K in keyof TFormbricksWebsite]: TFormbricksWebsite[K] extends Function ? K : never;
+// }[keyof TFormbricksWebsite];
+
+export const loadFormbricksToProxy = async (prop: string, sdkType: "app" | "website", ...args: any[]) => {
+ const executeMethod = async () => {
+ try {
+ // @ts-expect-error
+ return await (window.formbricks[prop] as Function)(...args);
+ } catch (error) {
+ console.error(`🧱 Formbricks - Global error: ${error}`);
+ throw error;
+ }
+ };
+
+ if (!isInitialized) {
+ if (isInitializing) {
+ methodQueue.add(executeMethod);
+ } else {
+ if (prop === "init") {
+ isInitializing = true;
+
+ const initialize = async () => {
+ const { apiHost } = args[0];
+ const loadSDKResult = await wrapThrowsAsync(loadFormbricksSDK)(apiHost, sdkType);
+
+ if (!loadSDKResult.ok) {
+ isInitializing = false;
+ console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
+ return;
+ }
+
+ try {
+ const result = await (window.formbricks[prop] as Function)(...args);
+ isInitialized = true;
+ isInitializing = false;
+
+ return result;
+ } catch (error) {
+ isInitializing = false;
+ console.error(`🧱 Formbricks - Global error: ${error}`);
+ throw error;
+ }
+ };
+
+ methodQueue.add(initialize);
+ } else {
+ console.error(
+ "🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
+ );
+ return;
+ }
+ }
+ } else {
+ // @ts-expect-error
+ if (window.formbricks && typeof window.formbricks[prop] !== "function") {
+ console.error(
+ `🧱 Formbricks - Global error: Formbricks ${sdkType} SDK does not support method ${String(prop)}`
+ );
+ return;
+ }
+
+ methodQueue.add(executeMethod);
+ return;
+ }
+};
diff --git a/packages/js/src/website.ts b/packages/js/src/website.ts
index ec41c93dc0..2b7e035d90 100644
--- a/packages/js/src/website.ts
+++ b/packages/js/src/website.ts
@@ -1,7 +1,7 @@
import { TFormbricksApp } from "@formbricks/js-core/app";
import { TFormbricksWebsite } from "@formbricks/js-core/website";
-import { Result, wrapThrowsAsync } from "../../types/errorHandlers";
+import { loadFormbricksToProxy } from "./shared/loadFormbricks";
declare global {
interface Window {
@@ -9,97 +9,11 @@ declare global {
}
}
-// load the sdk, return the result
-const loadFormbricksWebsiteSDK = async (apiHost: string): Promise> => {
- if (!window.formbricks) {
- const res = await fetch(`${apiHost}/api/packages/website`);
-
- // failed to fetch the app package
- if (!res.ok) {
- return { ok: false, error: new Error("Failed to load Formbricks Website SDK") };
- }
-
- const sdkScript = await res.text();
- const scriptTag = document.createElement("script");
- scriptTag.innerHTML = sdkScript;
- document.head.appendChild(scriptTag);
-
- const getFormbricks = async () =>
- new Promise((resolve, reject) => {
- const checkInterval = setInterval(() => {
- if (window.formbricks) {
- clearInterval(checkInterval);
- resolve();
- }
- }, 100);
-
- setTimeout(() => {
- clearInterval(checkInterval);
- reject(new Error("Formbricks Website SDK loading timed out"));
- }, 10000);
- });
-
- try {
- await getFormbricks();
- return { ok: true, data: undefined };
- } catch (error: any) {
- // formbricks loading failed, return the error
- return {
- ok: false,
- error: new Error(error.message ?? "Failed to load Formbricks Website SDK"),
- };
- }
- }
-
- return { ok: true, data: undefined };
-};
-
-type FormbricksWebsiteMethods = {
- [K in keyof TFormbricksWebsite]: TFormbricksWebsite[K] extends Function ? K : never;
-}[keyof TFormbricksWebsite];
-
const formbricksProxyHandler: ProxyHandler = {
get(_target, prop, _receiver) {
- return async (...args: any[]) => {
- if (!window.formbricks) {
- if (prop !== "init") {
- console.error(
- "🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
- );
- return;
- }
-
- // still need to check if the apiHost is passed
- if (!args[0]) {
- console.error("🧱 Formbricks - Global error: You need to pass the apiHost as the first argument");
- return;
- }
-
- const { apiHost } = args[0];
- const loadSDKResult = await wrapThrowsAsync(loadFormbricksWebsiteSDK)(apiHost);
-
- if (!loadSDKResult.ok) {
- console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
- return;
- }
- }
-
- if (window.formbricks && typeof window.formbricks[prop as FormbricksWebsiteMethods] !== "function") {
- console.error(
- `🧱 Formbricks - Global error: Formbricks Website SDK does not support method ${String(prop)}`
- );
- return;
- }
-
- try {
- return (window.formbricks[prop as FormbricksWebsiteMethods] as Function)(...args);
- } catch (error) {
- console.error(`🧱 Formbricks - Global error: Something went wrong: ${error}`);
- return;
- }
- };
+ return (...args: any[]) => loadFormbricksToProxy(prop as string, "website", ...args);
},
};
-const formbricksApp: TFormbricksWebsite = new Proxy({} as TFormbricksWebsite, formbricksProxyHandler);
-export default formbricksApp;
+const formbricksWebsite: TFormbricksWebsite = new Proxy({} as TFormbricksWebsite, formbricksProxyHandler);
+export default formbricksWebsite;