Compare commits

...

4 Commits

Author SHA1 Message Date
Dhruwang 734f1f4bf2 added cache no-store when formbricksDebug is enabled 2025-04-01 14:01:15 +07:00
pandeymangg 752cdd6ee9 fix tests 2025-03-31 18:11:33 +07:00
pandeymangg 1672eefe50 Merge branch 'main' into fix/hidden-fields-backwards-compatibility 2025-03-31 17:50:30 +07:00
pandeymangg fecedb7d30 fix: hidden fields backwards compatibility 2025-03-31 17:50:04 +07:00
20 changed files with 169 additions and 54 deletions
+5 -3
View File
@@ -6,10 +6,11 @@ import { makeRequest } from "../../utils/make-request";
export class AttributeAPI { export class AttributeAPI {
private appUrl: string; private appUrl: string;
private environmentId: string; private environmentId: string;
private isDebug: boolean;
constructor(appUrl: string, environmentId: string) { constructor(appUrl: string, environmentId: string, isDebug: boolean) {
this.appUrl = appUrl; this.appUrl = appUrl;
this.environmentId = environmentId; this.environmentId = environmentId;
this.isDebug = isDebug;
} }
async update( async update(
@@ -25,7 +26,8 @@ export class AttributeAPI {
this.appUrl, this.appUrl,
`/api/v1/client/${this.environmentId}/contacts/${attributeUpdateInput.userId}/attributes`, `/api/v1/client/${this.environmentId}/contacts/${attributeUpdateInput.userId}/attributes`,
"PUT", "PUT",
{ attributes } { attributes },
this.isDebug
); );
} }
} }
+10 -2
View File
@@ -6,15 +6,23 @@ import { makeRequest } from "../../utils/make-request";
export class DisplayAPI { export class DisplayAPI {
private appUrl: string; private appUrl: string;
private environmentId: string; private environmentId: string;
private isDebug: boolean;
constructor(appUrl: string, environmentId: string) { constructor(appUrl: string, environmentId: string, isDebug: boolean) {
this.appUrl = appUrl; this.appUrl = appUrl;
this.environmentId = environmentId; this.environmentId = environmentId;
this.isDebug = isDebug;
} }
async create( async create(
displayInput: Omit<TDisplayCreateInput, "environmentId"> displayInput: Omit<TDisplayCreateInput, "environmentId">
): Promise<Result<{ id: string }, ApiErrorResponse>> { ): Promise<Result<{ id: string }, ApiErrorResponse>> {
return makeRequest(this.appUrl, `/api/v1/client/${this.environmentId}/displays`, "POST", displayInput); return makeRequest(
this.appUrl,
`/api/v1/client/${this.environmentId}/displays`,
"POST",
displayInput,
this.isDebug
);
} }
} }
+10 -2
View File
@@ -6,13 +6,21 @@ import { makeRequest } from "../../utils/make-request";
export class EnvironmentAPI { export class EnvironmentAPI {
private appUrl: string; private appUrl: string;
private environmentId: string; private environmentId: string;
private isDebug: boolean;
constructor(appUrl: string, environmentId: string) { constructor(appUrl: string, environmentId: string, isDebug: boolean) {
this.appUrl = appUrl; this.appUrl = appUrl;
this.environmentId = environmentId; this.environmentId = environmentId;
this.isDebug = isDebug;
} }
async getState(): Promise<Result<TJsEnvironmentState, ApiErrorResponse>> { async getState(): Promise<Result<TJsEnvironmentState, ApiErrorResponse>> {
return makeRequest(this.appUrl, `/api/v1/client/${this.environmentId}/environment`, "GET"); return makeRequest(
this.appUrl,
`/api/v1/client/${this.environmentId}/environment`,
"GET",
undefined,
this.isDebug
);
} }
} }
+7 -6
View File
@@ -15,13 +15,14 @@ export class Client {
environment: EnvironmentAPI; environment: EnvironmentAPI;
constructor(options: ApiConfig) { constructor(options: ApiConfig) {
const { appUrl, environmentId } = options; const { appUrl, environmentId, isDebug } = options;
const isDebugMode = isDebug ?? false;
this.response = new ResponseAPI(appUrl, environmentId); this.response = new ResponseAPI(appUrl, environmentId, isDebugMode);
this.display = new DisplayAPI(appUrl, environmentId); this.display = new DisplayAPI(appUrl, environmentId, isDebugMode);
this.attribute = new AttributeAPI(appUrl, environmentId); this.attribute = new AttributeAPI(appUrl, environmentId, isDebugMode);
this.storage = new StorageAPI(appUrl, environmentId); this.storage = new StorageAPI(appUrl, environmentId);
this.user = new UserAPI(appUrl, environmentId); this.user = new UserAPI(appUrl, environmentId, isDebugMode);
this.environment = new EnvironmentAPI(appUrl, environmentId); this.environment = new EnvironmentAPI(appUrl, environmentId, isDebugMode);
} }
} }
+24 -11
View File
@@ -8,16 +8,23 @@ type TResponseUpdateInputWithResponseId = TResponseUpdateInput & { responseId: s
export class ResponseAPI { export class ResponseAPI {
private appUrl: string; private appUrl: string;
private environmentId: string; private environmentId: string;
private isDebug: boolean;
constructor(appUrl: string, environmentId: string) { constructor(appUrl: string, environmentId: string, isDebug: boolean) {
this.appUrl = appUrl; this.appUrl = appUrl;
this.environmentId = environmentId; this.environmentId = environmentId;
this.isDebug = isDebug;
} }
async create( async create(
responseInput: Omit<TResponseInput, "environmentId"> responseInput: Omit<TResponseInput, "environmentId">
): Promise<Result<{ id: string }, ApiErrorResponse>> { ): Promise<Result<{ id: string }, ApiErrorResponse>> {
return makeRequest(this.appUrl, `/api/v1/client/${this.environmentId}/responses`, "POST", responseInput); return makeRequest(
this.appUrl,
`/api/v1/client/${this.environmentId}/responses`,
"POST",
responseInput,
this.isDebug
);
} }
async update({ async update({
@@ -29,13 +36,19 @@ export class ResponseAPI {
variables, variables,
language, language,
}: TResponseUpdateInputWithResponseId): Promise<Result<object, ApiErrorResponse>> { }: TResponseUpdateInputWithResponseId): Promise<Result<object, ApiErrorResponse>> {
return makeRequest(this.appUrl, `/api/v1/client/${this.environmentId}/responses/${responseId}`, "PUT", { return makeRequest(
finished, this.appUrl,
endingId, `/api/v1/client/${this.environmentId}/responses/${responseId}`,
data, "PUT",
ttc, {
variables, finished,
language, endingId,
}); data,
ttc,
variables,
language,
},
this.isDebug
);
} }
} }
+13 -5
View File
@@ -5,10 +5,12 @@ import { makeRequest } from "../../utils/make-request";
export class UserAPI { export class UserAPI {
private appUrl: string; private appUrl: string;
private environmentId: string; private environmentId: string;
private isDebug: boolean;
constructor(appUrl: string, environmentId: string) { constructor(appUrl: string, environmentId: string, isDebug: boolean) {
this.appUrl = appUrl; this.appUrl = appUrl;
this.environmentId = environmentId; this.environmentId = environmentId;
this.isDebug = isDebug;
} }
async createOrUpdate(userUpdateInput: { userId: string; attributes?: Record<string, string> }): Promise< async createOrUpdate(userUpdateInput: { userId: string; attributes?: Record<string, string> }): Promise<
@@ -37,9 +39,15 @@ export class UserAPI {
attributes[key] = String(userUpdateInput.attributes[key]); attributes[key] = String(userUpdateInput.attributes[key]);
} }
return makeRequest(this.appUrl, `/api/v2/client/${this.environmentId}/user`, "POST", { return makeRequest(
userId: userUpdateInput.userId, this.appUrl,
attributes, `/api/v2/client/${this.environmentId}/user`,
}); "POST",
{
userId: userUpdateInput.userId,
attributes,
},
this.isDebug
);
} }
} }
+1
View File
@@ -3,6 +3,7 @@ import { type ApiErrorResponse } from "@formbricks/types/errors";
export interface ApiConfig { export interface ApiConfig {
environmentId: string; environmentId: string;
appUrl: string; appUrl: string;
isDebug?: boolean;
} }
export type ApiResponse = ApiSuccessResponse | ApiErrorResponse; export type ApiResponse = ApiSuccessResponse | ApiErrorResponse;
+3 -2
View File
@@ -6,16 +6,17 @@ export const makeRequest = async <T>(
appUrl: string, appUrl: string,
endpoint: string, endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE", method: "GET" | "POST" | "PUT" | "DELETE",
data?: unknown data?: unknown,
isDebug?: boolean
): Promise<Result<T, ApiErrorResponse>> => { ): Promise<Result<T, ApiErrorResponse>> => {
const url = new URL(appUrl + endpoint); const url = new URL(appUrl + endpoint);
const body = data ? JSON.stringify(data) : undefined; const body = data ? JSON.stringify(data) : undefined;
const res = await wrapThrowsAsync(fetch)(url.toString(), { const res = await wrapThrowsAsync(fetch)(url.toString(), {
method, method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
...(isDebug && { cache: "no-store" }),
body, body,
}); });
+7 -2
View File
@@ -7,6 +7,7 @@ import { checkPageUrl } from "@/lib/survey/no-code-action";
import * as Attribute from "@/lib/user/attribute"; import * as Attribute from "@/lib/user/attribute";
import * as User from "@/lib/user/user"; import * as User from "@/lib/user/user";
import { type TConfigInput, type TLegacyConfigInput } from "@/types/config"; import { type TConfigInput, type TLegacyConfigInput } from "@/types/config";
import { type TTrackProperties } from "@/types/survey";
const queue = new CommandQueue(); const queue = new CommandQueue();
@@ -67,8 +68,12 @@ const logout = async (): Promise<void> => {
await queue.wait(); await queue.wait();
}; };
const track = async (code: string): Promise<void> => { /**
queue.add(Action.trackCodeAction, true, code); * @param code - The code of the action to track
* @param properties - Optional properties to set, like the hidden fields (deprecated, hidden fields will be removed in a future version)
*/
const track = async (code: string, properties?: TTrackProperties): Promise<void> => {
queue.add(Action.trackCodeAction, true, code, properties as unknown);
await queue.wait(); await queue.wait();
}; };
@@ -4,9 +4,13 @@ import { checkSetup } from "@/lib/common/setup";
import { wrapThrowsAsync } from "@/lib/common/utils"; import { wrapThrowsAsync } from "@/lib/common/utils";
import type { Result } from "@/types/error"; import type { Result } from "@/types/error";
export type TCommandQueueCommand = (
...args: any[]
) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>;
export class CommandQueue { export class CommandQueue {
private queue: { private queue: {
command: (...args: any[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>; command: TCommandQueueCommand;
checkSetup: boolean; checkSetup: boolean;
commandArgs: any[]; commandArgs: any[];
}[] = []; }[] = [];
@@ -14,11 +18,7 @@ export class CommandQueue {
private resolvePromise: (() => void) | null = null; private resolvePromise: (() => void) | null = null;
private commandPromise: Promise<void> | null = null; private commandPromise: Promise<void> | null = null;
public add<A>( public add<A>(command: TCommandQueueCommand, shouldCheckSetup = true, ...args: A[]): void {
command: (...args: A[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>,
shouldCheckSetup = true,
...args: A[]
): void {
this.queue.push({ command, checkSetup: shouldCheckSetup, commandArgs: args }); this.queue.push({ command, checkSetup: shouldCheckSetup, commandArgs: args });
if (!this.running) { if (!this.running) {
+1
View File
@@ -156,6 +156,7 @@ export const setup = async (
addWidgetContainer(); addWidgetContainer();
if ( if (
!isDebug &&
existingConfig?.environment && existingConfig?.environment &&
existingConfig.environmentId === configInput.environmentId && existingConfig.environmentId === configInput.environmentId &&
existingConfig.appUrl === configInput.appUrl existingConfig.appUrl === configInput.appUrl
+38 -1
View File
@@ -1,3 +1,4 @@
import { Logger } from "@/lib/common/logger";
import type { import type {
TEnvironmentState, TEnvironmentState,
TEnvironmentStateActionClass, TEnvironmentStateActionClass,
@@ -8,7 +9,11 @@ import type {
TUserState, TUserState,
} from "@/types/config"; } from "@/types/config";
import type { Result } from "@/types/error"; import type { Result } from "@/types/error";
import { type TActionClassNoCodeConfig, type TActionClassPageUrlRule } from "@/types/survey"; import {
type TActionClassNoCodeConfig,
type TActionClassPageUrlRule,
type TTrackProperties,
} from "@/types/survey";
// Helper function to calculate difference in days between two dates // Helper function to calculate difference in days between two dates
export const diffInDays = (date1: Date, date2: Date): number => { export const diffInDays = (date1: Date, date2: Date): number => {
@@ -225,6 +230,38 @@ export const handleUrlFilters = (urlFilters: TActionClassNoCodeConfig["urlFilter
return isMatch; return isMatch;
}; };
export const handleHiddenFields = (
hiddenFieldsConfig: TEnvironmentStateSurvey["hiddenFields"],
hiddenFields?: TTrackProperties["hiddenFields"]
): TTrackProperties["hiddenFields"] => {
const logger = Logger.getInstance();
const { enabled: enabledHiddenFields, fieldIds: hiddenFieldIds } = hiddenFieldsConfig;
let hiddenFieldsObject: TTrackProperties["hiddenFields"] = {};
if (!enabledHiddenFields) {
logger.error("Hidden fields are not enabled for this survey");
} else if (hiddenFieldIds && hiddenFields) {
const unknownHiddenFields: string[] = [];
hiddenFieldsObject = Object.keys(hiddenFields).reduce<TTrackProperties["hiddenFields"]>((acc, key) => {
if (hiddenFieldIds.includes(key)) {
acc[key] = hiddenFields[key];
} else {
unknownHiddenFields.push(key);
}
return acc;
}, {});
if (unknownHiddenFields.length > 0) {
logger.error(
`Unknown hidden fields: ${unknownHiddenFields.join(", ")}. Please add them to the survey hidden fields.`
);
}
}
return hiddenFieldsObject;
};
export const evaluateNoCodeConfigClick = ( export const evaluateNoCodeConfigClick = (
targetElement: HTMLElement, targetElement: HTMLElement,
action: TEnvironmentStateActionClass action: TEnvironmentStateActionClass
@@ -2,7 +2,7 @@
import { FormbricksAPI } from "@formbricks/api"; import { FormbricksAPI } from "@formbricks/api";
import { Config } from "@/lib/common/config"; import { Config } from "@/lib/common/config";
import { Logger } from "@/lib/common/logger"; import { Logger } from "@/lib/common/logger";
import { filterSurveys } from "@/lib/common/utils"; import { filterSurveys , getIsDebug } from "@/lib/common/utils";
import type { TConfigInput, TEnvironmentState } from "@/types/config"; import type { TConfigInput, TEnvironmentState } from "@/types/config";
import { type ApiErrorResponse, type Result, err, ok } from "@/types/error"; import { type ApiErrorResponse, type Result, err, ok } from "@/types/error";
@@ -20,7 +20,7 @@ export const fetchEnvironmentState = async ({
environmentId, environmentId,
}: TConfigInput): Promise<Result<TEnvironmentState, ApiErrorResponse>> => { }: TConfigInput): Promise<Result<TEnvironmentState, ApiErrorResponse>> => {
const url = `${appUrl}/api/v1/client/${environmentId}/environment`; const url = `${appUrl}/api/v1/client/${environmentId}/environment`;
const api = new FormbricksAPI({ appUrl, environmentId }); const api = new FormbricksAPI({ appUrl, environmentId, isDebug: getIsDebug() });
try { try {
const response = await api.client.environment.getState(); const response = await api.client.environment.getState();
+12 -4
View File
@@ -2,14 +2,20 @@ import { Config } from "@/lib/common/config";
import { Logger } from "@/lib/common/logger"; import { Logger } from "@/lib/common/logger";
import { triggerSurvey } from "@/lib/survey/widget"; import { triggerSurvey } from "@/lib/survey/widget";
import { type InvalidCodeError, type NetworkError, type Result, err, okVoid } from "@/types/error"; import { type InvalidCodeError, type NetworkError, type Result, err, okVoid } from "@/types/error";
import { type TTrackProperties } from "@/types/survey";
/** /**
* Tracks an action name and triggers associated surveys * Tracks an action name and triggers associated surveys
* @param name - The name of the action to track * @param name - The name of the action to track
* @param alias - Optional alias for the action name * @param alias - Optional alias for the action name
* @param properties - Optional properties to set, like the hidden fields (deprecated, hidden fields will be removed in a future version)
* @returns Result indicating success or network error * @returns Result indicating success or network error
*/ */
export const trackAction = async (name: string, alias?: string): Promise<Result<void, NetworkError>> => { export const trackAction = async (
name: string,
alias?: string,
properties?: TTrackProperties
): Promise<Result<void, NetworkError>> => {
const logger = Logger.getInstance(); const logger = Logger.getInstance();
const appConfig = Config.getInstance(); const appConfig = Config.getInstance();
@@ -24,7 +30,7 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
for (const survey of activeSurveys) { for (const survey of activeSurveys) {
for (const trigger of survey.triggers) { for (const trigger of survey.triggers) {
if (trigger.actionClass.name === name) { if (trigger.actionClass.name === name) {
await triggerSurvey(survey, name); await triggerSurvey(survey, name, properties);
} }
} }
} }
@@ -38,10 +44,12 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
/** /**
* Tracks an action by its code and triggers associated surveys (used for code actions only) * Tracks an action by its code and triggers associated surveys (used for code actions only)
* @param code - The action code to track * @param code - The action code to track
* @param properties - Optional properties to set, like the hidden fields (deprecated, hidden fields will be removed in a future version)
* @returns Result indicating success, network error, or invalid code error * @returns Result indicating success, network error, or invalid code error
*/ */
export const trackCodeAction = async ( export const trackCodeAction = async (
code: string code: string,
properties?: TTrackProperties
): Promise<Result<void, NetworkError> | Result<void, InvalidCodeError>> => { ): Promise<Result<void, NetworkError> | Result<void, InvalidCodeError>> => {
const appConfig = Config.getInstance(); const appConfig = Config.getInstance();
@@ -61,7 +69,7 @@ export const trackCodeAction = async (
}); });
} }
return trackAction(actionClass.name, code); return trackAction(actionClass.name, code, properties);
}; };
export const trackNoCodeAction = (name: string): Promise<Result<void, NetworkError>> => { export const trackNoCodeAction = (name: string): Promise<Result<void, NetworkError>> => {
@@ -33,6 +33,7 @@ vi.mock("@/lib/common/logger", () => ({
vi.mock("@/lib/common/utils", () => ({ vi.mock("@/lib/common/utils", () => ({
shouldDisplayBasedOnPercentage: vi.fn(), shouldDisplayBasedOnPercentage: vi.fn(),
handleHiddenFields: vi.fn(),
})); }));
vi.mock("@/lib/survey/widget", () => ({ vi.mock("@/lib/survey/widget", () => ({
@@ -100,10 +101,10 @@ describe("survey/action.ts", () => {
filteredSurveys: [mockSurvey], filteredSurveys: [mockSurvey],
}); });
const result = await trackAction("testAction"); const result = await trackAction("testAction", undefined);
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
expect(triggerSurvey).toHaveBeenCalledWith(mockSurvey, "testAction"); expect(triggerSurvey).toHaveBeenCalledWith(mockSurvey, "testAction", undefined);
}); });
test("handles multiple matching surveys", async () => { test("handles multiple matching surveys", async () => {
@@ -40,6 +40,7 @@ vi.mock("@/lib/common/utils", () => ({
getStyling: vi.fn(), getStyling: vi.fn(),
shouldDisplayBasedOnPercentage: vi.fn(), shouldDisplayBasedOnPercentage: vi.fn(),
wrapThrowsAsync: vi.fn(), wrapThrowsAsync: vi.fn(),
handleHiddenFields: vi.fn(),
})); }));
describe("widget-file", () => { describe("widget-file", () => {
+19 -3
View File
@@ -7,9 +7,11 @@ import {
filterSurveys, filterSurveys,
getLanguageCode, getLanguageCode,
getStyling, getStyling,
handleHiddenFields,
shouldDisplayBasedOnPercentage, shouldDisplayBasedOnPercentage,
} from "@/lib/common/utils"; } from "@/lib/common/utils";
import { type TEnvironmentStateSurvey, type TUserState } from "@/types/config"; import { type TEnvironmentStateSurvey, type TUserState } from "@/types/config";
import { type TTrackProperties } from "@/types/survey";
let isSurveyRunning = false; let isSurveyRunning = false;
@@ -17,7 +19,11 @@ export const setIsSurveyRunning = (value: boolean): void => {
isSurveyRunning = value; isSurveyRunning = value;
}; };
export const triggerSurvey = async (survey: TEnvironmentStateSurvey, action?: string): Promise<void> => { export const triggerSurvey = async (
survey: TEnvironmentStateSurvey,
action?: string,
properties?: TTrackProperties
): Promise<void> => {
const logger = Logger.getInstance(); const logger = Logger.getInstance();
// Check if the survey should be displayed based on displayPercentage // Check if the survey should be displayed based on displayPercentage
@@ -29,10 +35,19 @@ export const triggerSurvey = async (survey: TEnvironmentStateSurvey, action?: st
} }
} }
await renderWidget(survey, action); const hiddenFieldsObject: TTrackProperties["hiddenFields"] = handleHiddenFields(
survey.hiddenFields,
properties?.hiddenFields
);
await renderWidget(survey, action, hiddenFieldsObject);
}; };
export const renderWidget = async (survey: TEnvironmentStateSurvey, action?: string): Promise<void> => { export const renderWidget = async (
survey: TEnvironmentStateSurvey,
action?: string,
hiddenFieldsObject?: TTrackProperties["hiddenFields"]
): Promise<void> => {
const logger = Logger.getInstance(); const logger = Logger.getInstance();
const config = Config.getInstance(); const config = Config.getInstance();
const timeoutStack = TimeoutStack.getInstance(); const timeoutStack = TimeoutStack.getInstance();
@@ -87,6 +102,7 @@ export const renderWidget = async (survey: TEnvironmentStateSurvey, action?: str
languageCode, languageCode,
placement, placement,
styling: getStyling(project, survey), styling: getStyling(project, survey),
hiddenFieldsRecord: hiddenFieldsObject,
onDisplayCreated: () => { onDisplayCreated: () => {
const existingDisplays = config.get().user.data.displays; const existingDisplays = config.get().user.data.displays;
const newDisplay = { surveyId: survey.id, createdAt: new Date() }; const newDisplay = { surveyId: survey.id, createdAt: new Date() };
+2 -2
View File
@@ -2,7 +2,7 @@
import { FormbricksAPI } from "@formbricks/api"; import { FormbricksAPI } from "@formbricks/api";
import { Config } from "@/lib/common/config"; import { Config } from "@/lib/common/config";
import { Logger } from "@/lib/common/logger"; import { Logger } from "@/lib/common/logger";
import { filterSurveys } from "@/lib/common/utils"; import { filterSurveys , getIsDebug } from "@/lib/common/utils";
import { type TUpdates, type TUserState } from "@/types/config"; import { type TUpdates, type TUserState } from "@/types/config";
import { type ApiErrorResponse, type Result, type ResultError, err, ok, okVoid } from "@/types/error"; import { type ApiErrorResponse, type Result, type ResultError, err, ok, okVoid } from "@/types/error";
@@ -26,7 +26,7 @@ export const sendUpdatesToBackend = async ({
const url = `${appUrl}/api/v1/client/${environmentId}/user`; const url = `${appUrl}/api/v1/client/${environmentId}/user`;
try { try {
const api = new FormbricksAPI({ appUrl, environmentId }); const api = new FormbricksAPI({ appUrl, environmentId, isDebug: getIsDebug() });
const response = await api.client.user.createOrUpdate({ const response = await api.client.user.createOrUpdate({
userId: updates.userId, userId: updates.userId,
+4
View File
@@ -75,3 +75,7 @@ export type TActionClassNoCodeConfig =
rule: TActionClassPageUrlRule; rule: TActionClassPageUrlRule;
}[]; }[];
}; };
export interface TTrackProperties {
hiddenFields: Record<string, string | number | string[]>;
}
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@formbricks/js", "name": "@formbricks/js",
"license": "MIT", "license": "MIT",
"version": "4.0.0", "version": "4.1.0",
"description": "Formbricks-js allows you to connect your index to Formbricks, display surveys and trigger events.", "description": "Formbricks-js allows you to connect your index to Formbricks, display surveys and trigger events.",
"homepage": "https://formbricks.com", "homepage": "https://formbricks.com",
"repository": { "repository": {