Merge branch 'main' into fix/product-settings-form

This commit is contained in:
pandeymangg
2024-05-22 18:46:32 +05:30
11 changed files with 206 additions and 206 deletions

View File

@@ -20,7 +20,9 @@ Formbricks v2.0 comes with huge features such as Multi-Language Surveys and Adva
and
<Note>
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/)
</Note>
### Steps to Migrate
@@ -35,7 +37,7 @@ To run all these steps, please navigate to the `formbricks` folder where your `d
<CodeGroup title="Backup Postgres">
```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
```
</CodeGroup>
@@ -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.
</Note>
2. Stop the running Formbricks instance & remove the related containers:
2. Pull the latest version of Formbricks:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker-compose pull
```
</CodeGroup>
</Col>
3. Stop the running Formbricks instance & remove the related containers:
<Col>
<CodeGroup title="Stop the containers">
@@ -63,7 +77,7 @@ docker-compose down
</CodeGroup>
</Col>
3. Restarting the containers will automatically pull the latest version of Formbricks:
4. Restarting the containers with the latest version of Formbricks:
<Col>
<CodeGroup title="Restart the containers">
@@ -75,7 +89,7 @@ docker-compose up -d
</CodeGroup>
</Col>
4. Now let's migrate the data to the latest schema:
5. Now let's migrate the data to the latest schema:
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
@@ -83,6 +97,7 @@ docker-compose up -d
<CodeGroup title="Migrate the data">
```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

View File

@@ -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;
};

View File

@@ -59,7 +59,7 @@ export const POST = async (request: Request): Promise<Response> => {
url: responseInput?.meta?.url,
userAgent: {
browser: agent?.browser.name,
device: agent?.device.type,
device: agent?.device.type || "desktop",
os: agent?.os.name,
},
country: country,

View File

@@ -82,7 +82,7 @@ export const POST = async (request: Request, context: Context): Promise<Response
url: responseInput?.meta?.url,
userAgent: {
browser: agent?.browser.name,
device: agent?.device.type,
device: agent?.device.type || "desktop",
os: agent?.os.name,
},
country: country,

View File

@@ -114,7 +114,7 @@ export const GET = async (
const noCodeActionClasses = actionClasses.filter((actionClass) => 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,

View File

@@ -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": {

View File

@@ -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<Result<void>> => {
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<void>((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<TFormbricksApp> = {
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);
},
};

View File

@@ -1,2 +0,0 @@
export { default as formbricksApp } from "./app";
export { default as formbricksWebsite } from "./website";

View File

@@ -0,0 +1,38 @@
// Simple queue for formbricks methods
export class MethodQueue {
private queue: (() => Promise<void>)[] = [];
private isExecuting = false;
add = (method: () => Promise<void>) => {
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 = [];
};
}

View File

@@ -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<Result<void>> => {
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<void>((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;
}
};

View File

@@ -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<Result<void>> => {
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<void>((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<TFormbricksWebsite> = {
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;