fix: renames init to setup (#4714)

This commit is contained in:
Anshuman Pandey
2025-02-05 15:42:14 +05:30
committed by GitHub
parent 96a4d02c80
commit a86c1738d1
8 changed files with 112 additions and 130 deletions

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useSyncExternalStore } from "react";
import { SurveyWebView } from "@/components/survey-web-view";
import { init } from "@/lib/common/initialize";
import { Logger } from "@/lib/common/logger";
import { setup } from "@/lib/common/setup";
import { SurveyStore } from "@/lib/survey/store";
interface FormbricksProps {
@@ -15,9 +15,9 @@ const logger = Logger.getInstance();
export function Formbricks({ appUrl, environmentId }: FormbricksProps): React.JSX.Element | null {
// initializes sdk
useEffect(() => {
const initialize = async (): Promise<void> => {
const setupFormbricks = async (): Promise<void> => {
try {
await init({
await setup({
environmentId,
appUrl,
});
@@ -26,7 +26,7 @@ export function Formbricks({ appUrl, environmentId }: FormbricksProps): React.JS
}
};
initialize().catch(() => {
setupFormbricks().catch(() => {
logger.debug("Initialization error");
});
}, [environmentId, appUrl]);

View File

@@ -1,12 +1,12 @@
/* eslint-disable no-console -- we need to log global errors */
import { checkInitialized } from "@/lib/common/initialize";
import { checkSetup } from "@/lib/common/setup";
import { wrapThrowsAsync } from "@/lib/common/utils";
import type { Result } from "@/types/error";
export class CommandQueue {
private queue: {
command: (...args: any[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>;
checkInitialized: boolean;
checkSetup: boolean;
commandArgs: any[];
}[] = [];
private running = false;
@@ -15,10 +15,10 @@ export class CommandQueue {
public add<A>(
command: (...args: A[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>,
shouldCheckInitialized = true,
shouldCheckSetup = true,
...args: A[]
): void {
this.queue.push({ command, checkInitialized: shouldCheckInitialized, commandArgs: args });
this.queue.push({ command, checkSetup: shouldCheckSetup, commandArgs: args });
if (!this.running) {
this.commandPromise = new Promise((resolve) => {
@@ -41,12 +41,12 @@ export class CommandQueue {
if (!currentItem) continue;
// make sure formbricks is initialized
if (currentItem.checkInitialized) {
// make sure formbricks is setup
if (currentItem.checkSetup) {
// call different function based on package type
const initResult = checkInitialized();
const setupResult = checkSetup();
if (!initResult.ok) {
if (!setupResult.ok) {
continue;
}
}

View File

@@ -15,26 +15,26 @@ import {
type MissingFieldError,
type MissingPersonError,
type NetworkError,
type NotInitializedError,
type NotSetupError,
type Result,
err,
okVoid,
} from "@/types/error";
let isInitialized = false;
let isSetup = false;
export const setIsInitialize = (state: boolean): void => {
isInitialized = state;
export const setIsSetup = (state: boolean): void => {
isSetup = state;
};
export const init = async (
export const setup = async (
configInput: TConfigInput
): Promise<Result<void, MissingFieldError | NetworkError | MissingPersonError>> => {
const appConfig = RNConfig.getInstance();
const logger = Logger.getInstance();
if (isInitialized) {
logger.debug("Already initialized, skipping initialization.");
if (isSetup) {
logger.debug("Already set up, skipping setup.");
return okVoid();
}
@@ -46,20 +46,20 @@ export const init = async (
logger.debug("No existing configuration found.");
}
// formbricks is in error state, skip initialization
// formbricks is in error state, skip setup
if (existingConfig?.status.value === "error") {
logger.debug("Formbricks was set to an error state.");
const expiresAt = existingConfig.status.expiresAt;
if (expiresAt && isNowExpired(expiresAt)) {
logger.debug("Error state is not expired, skipping initialization");
logger.debug("Error state is not expired, skipping setup");
return okVoid();
}
logger.debug("Error state is expired. Continue with initialization.");
logger.debug("Error state is expired. Continue with setup.");
}
logger.debug("Start initialize");
logger.debug("Start setup");
if (!configInput.environmentId) {
logger.debug("No environmentId provided");
@@ -83,7 +83,7 @@ export const init = async (
existingConfig.environmentId === configInput.environmentId &&
existingConfig.appUrl === configInput.appUrl
) {
logger.debug("Configuration fits init parameters.");
logger.debug("Configuration fits setup parameters.");
let isEnvironmentStateExpired = false;
let isUserStateExpired = false;
@@ -179,7 +179,7 @@ export const init = async (
void appConfig.resetConfig();
logger.debug("Syncing.");
// During init, if we don't have a valid config, we need to fetch the environment state
// During setup, if we don't have a valid config, we need to fetch the environment state
// but not the person state, we can set it to the default value.
// The person state will be fetched when the `setUserId` method is called.
@@ -207,7 +207,7 @@ export const init = async (
filteredSurveys,
});
} catch (e) {
await handleErrorOnFirstInit(e as { code: string; responseMessage: string });
await handleErrorOnFirstSetup(e as { code: string; responseMessage: string });
}
}
@@ -215,21 +215,21 @@ export const init = async (
addEventListeners();
addCleanupEventListeners();
setIsInitialize(true);
logger.debug("Initialized");
setIsSetup(true);
logger.debug("Set up complete");
// check page url if initialized after page load
// check page url if set up after page load
return okVoid();
};
export const checkInitialized = (): Result<void, NotInitializedError> => {
export const checkSetup = (): Result<void, NotSetupError> => {
const logger = Logger.getInstance();
logger.debug("Check if initialized");
logger.debug("Check if set up");
if (!isInitialized) {
if (!isSetup) {
return err({
code: "not_initialized",
message: "Formbricks not initialized. Call initialize() first.",
code: "not_setup",
message: "Formbricks is not set up. Call setup() first.",
});
}
@@ -237,21 +237,22 @@ export const checkInitialized = (): Result<void, NotInitializedError> => {
};
// eslint-disable-next-line @typescript-eslint/require-await -- disabled for now
export const deinitalize = async (): Promise<void> => {
export const tearDown = async (): Promise<void> => {
const logger = Logger.getInstance();
const appConfig = RNConfig.getInstance();
logger.debug("Setting person state to default");
logger.debug("Setting user state to default");
// clear the user state and set it to the default value
appConfig.update({
...appConfig.get(),
user: DEFAULT_USER_STATE_NO_USER_ID,
});
setIsInitialize(false);
setIsSetup(false);
removeAllEventListeners();
};
export const handleErrorOnFirstInit = async (e: {
export const handleErrorOnFirstSetup = async (e: {
code: string;
responseMessage: string;
}): Promise<never> => {
@@ -260,9 +261,7 @@ export const handleErrorOnFirstInit = async (e: {
if (e.code === "forbidden") {
logger.error(`Authorization error: ${e.responseMessage}`);
} else {
logger.error(
`Error during first initialization: ${e.code} - ${e.responseMessage}. Please try again later.`
);
logger.error(`Error during first setup: ${e.code} - ${e.responseMessage}. Please try again later.`);
}
// put formbricks in error state (by creating a new config) and throw error
@@ -277,5 +276,5 @@ export const handleErrorOnFirstInit = async (e: {
await AsyncStorage.setItem(RN_ASYNC_STORAGE_KEY, JSON.stringify(initialErrorConfig));
})();
throw new Error("Could not initialize formbricks");
throw new Error("Could not set up formbricks");
};

View File

@@ -1,11 +1,11 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { CommandQueue } from "@/lib/common/command-queue";
import { checkInitialized } from "@/lib/common/initialize";
import { checkSetup } from "@/lib/common/setup";
import { type Result } from "@/types/error";
// Mock the initialize module so we can control checkInitialized()
vi.mock("@/lib/common/initialize", () => ({
checkInitialized: vi.fn(),
// Mock the setup module so we can control checkSetup()
vi.mock("@/lib/common/setup", () => ({
checkSetup: vi.fn(),
}));
describe("CommandQueue", () => {
@@ -47,8 +47,8 @@ describe("CommandQueue", () => {
});
});
// We'll assume checkInitialized always ok for this test
vi.mocked(checkInitialized).mockReturnValue({ ok: true, data: undefined });
// We'll assume checkSetup always ok for this test
vi.mocked(checkSetup).mockReturnValue({ ok: true, data: undefined });
// Enqueue commands
queue.add(cmdA, true);
@@ -61,7 +61,7 @@ describe("CommandQueue", () => {
expect(executionOrder).toEqual(["A", "B", "C"]);
});
test("skips execution if checkInitialized() fails", async () => {
test("skips execution if checkSetup() fails", async () => {
const cmd = vi.fn(async (): Promise<void> => {
return new Promise((resolve) => {
setTimeout(() => {
@@ -70,12 +70,12 @@ describe("CommandQueue", () => {
});
});
// Force checkInitialized to fail
vi.mocked(checkInitialized).mockReturnValue({
// Force checkSetup to fail
vi.mocked(checkSetup).mockReturnValue({
ok: false,
error: {
code: "not_initialized",
message: "Not initialized",
code: "not_setup",
message: "Not setup",
},
});
@@ -86,7 +86,7 @@ describe("CommandQueue", () => {
expect(cmd).not.toHaveBeenCalled();
});
test("executes command if checkInitialized is false (no check)", async () => {
test("executes command if checkSetup is false (no check)", async () => {
const cmd = vi.fn(async (): Promise<Result<void, unknown>> => {
return new Promise((resolve) => {
setTimeout(() => {
@@ -95,8 +95,8 @@ describe("CommandQueue", () => {
});
});
// checkInitialized is irrelevant in this scenario, but let's mock it anyway
vi.mocked(checkInitialized).mockReturnValue({ ok: true, data: undefined });
// checkSetup is irrelevant in this scenario, but let's mock it anyway
vi.mocked(checkSetup).mockReturnValue({ ok: true, data: undefined });
// Here we pass 'false' for the second argument, so no check is performed
queue.add(cmd, false);
@@ -114,8 +114,8 @@ describe("CommandQueue", () => {
};
});
// Force checkInitialized to succeed
vi.mocked(checkInitialized).mockReturnValue({ ok: true, data: undefined });
// Force checkSetup to succeed
vi.mocked(checkSetup).mockReturnValue({ ok: true, data: undefined });
// Mock command that fails
const failingCmd = vi.fn(async () => {
@@ -151,7 +151,7 @@ describe("CommandQueue", () => {
});
});
vi.mocked(checkInitialized).mockReturnValue({ ok: true, data: undefined });
vi.mocked(checkSetup).mockReturnValue({ ok: true, data: undefined });
queue.add(cmd1, true);
queue.add(cmd2, true);

View File

@@ -1,4 +1,3 @@
// initialize.test.ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { RNConfig, RN_ASYNC_STORAGE_KEY } from "@/lib/common/config";
@@ -7,14 +6,8 @@ import {
addEventListeners,
removeAllEventListeners,
} from "@/lib/common/event-listeners";
import {
checkInitialized,
deinitalize,
handleErrorOnFirstInit,
init,
setIsInitialize,
} from "@/lib/common/initialize";
import { Logger } from "@/lib/common/logger";
import { checkSetup, handleErrorOnFirstSetup, setIsSetup, setup, tearDown } from "@/lib/common/setup";
import { filterSurveys, isNowExpired } from "@/lib/common/utils";
import { fetchEnvironmentState } from "@/lib/environment/state";
import { DEFAULT_USER_STATE_NO_USER_ID } from "@/lib/user/state";
@@ -77,7 +70,7 @@ vi.mock("@/lib/user/update", () => ({
sendUpdatesToBackend: vi.fn(),
}));
describe("initialize.ts", () => {
describe("setup.ts", () => {
let getInstanceConfigMock: MockInstance<() => RNConfig>;
let getInstanceLoggerMock: MockInstance<() => Logger>;
@@ -88,8 +81,8 @@ describe("initialize.ts", () => {
beforeEach(() => {
vi.clearAllMocks();
// By default, set isInitialize to false so we can test init logic from scratch
setIsInitialize(false);
// By default, set isSetup to false so we can test setup logic from scratch
setIsSetup(false);
getInstanceConfigMock = vi.spyOn(RNConfig, "getInstance");
getInstanceLoggerMock = vi.spyOn(Logger, "getInstance").mockReturnValue(mockLogger as unknown as Logger);
@@ -99,17 +92,17 @@ describe("initialize.ts", () => {
vi.restoreAllMocks();
});
describe("init()", () => {
test("returns ok if already initialized", async () => {
describe("setup()", () => {
test("returns ok if already setup", async () => {
getInstanceLoggerMock.mockReturnValue(mockLogger as unknown as Logger);
setIsInitialize(true);
const result = await init({ environmentId: "env_id", appUrl: "https://my.url" });
setIsSetup(true);
const result = await setup({ environmentId: "env_id", appUrl: "https://my.url" });
expect(result.ok).toBe(true);
expect(mockLogger.debug).toHaveBeenCalledWith("Already initialized, skipping initialization.");
expect(mockLogger.debug).toHaveBeenCalledWith("Already set up, skipping setup.");
});
test("fails if no environmentId is provided", async () => {
const result = await init({ environmentId: "", appUrl: "https://my.url" });
const result = await setup({ environmentId: "", appUrl: "https://my.url" });
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.code).toBe("missing_field");
@@ -117,14 +110,14 @@ describe("initialize.ts", () => {
});
test("fails if no appUrl is provided", async () => {
const result = await init({ environmentId: "env_123", appUrl: "" });
const result = await setup({ environmentId: "env_123", appUrl: "" });
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.code).toBe("missing_field");
}
});
test("skips init if existing config is in error state and not expired", async () => {
test("skips setup if existing config is in error state and not expired", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue({
environmentId: "env_123",
@@ -139,10 +132,10 @@ describe("initialize.ts", () => {
(isNowExpired as unknown as Mock).mockReturnValue(true);
const result = await init({ environmentId: "env_123", appUrl: "https://my.url" });
const result = await setup({ environmentId: "env_123", appUrl: "https://my.url" });
expect(result.ok).toBe(true);
expect(mockLogger.debug).toHaveBeenCalledWith("Formbricks was set to an error state.");
expect(mockLogger.debug).toHaveBeenCalledWith("Error state is not expired, skipping initialization");
expect(mockLogger.debug).toHaveBeenCalledWith("Error state is not expired, skipping setup");
});
test("proceeds if error state is expired", async () => {
@@ -158,10 +151,10 @@ describe("initialize.ts", () => {
getInstanceConfigMock.mockReturnValue(mockConfig as unknown as RNConfig);
const result = await init({ environmentId: "env_123", appUrl: "https://my.url" });
const result = await setup({ environmentId: "env_123", appUrl: "https://my.url" });
expect(result.ok).toBe(true);
expect(mockLogger.debug).toHaveBeenCalledWith("Formbricks was set to an error state.");
expect(mockLogger.debug).toHaveBeenCalledWith("Error state is expired. Continue with initialization.");
expect(mockLogger.debug).toHaveBeenCalledWith("Error state is expired. Continue with setup.");
});
test("uses existing config if environmentId/appUrl match, checks for expiration sync", async () => {
@@ -202,7 +195,7 @@ describe("initialize.ts", () => {
(filterSurveys as unknown as Mock).mockReturnValueOnce([{ name: "S1" }, { name: "S2" }]);
const result = await init({ environmentId: "env_123", appUrl: "https://my.url" });
const result = await setup({ environmentId: "env_123", appUrl: "https://my.url" });
expect(result.ok).toBe(true);
// environmentState was fetched
@@ -246,7 +239,7 @@ describe("initialize.ts", () => {
(filterSurveys as unknown as Mock).mockReturnValueOnce([{ name: "SurveyA" }]);
const result = await init({ environmentId: "envX", appUrl: "https://urlX" });
const result = await setup({ environmentId: "envX", appUrl: "https://urlX" });
expect(result.ok).toBe(true);
expect(mockLogger.debug).toHaveBeenCalledWith("No existing configuration found.");
expect(mockLogger.debug).toHaveBeenCalledWith(
@@ -269,7 +262,7 @@ describe("initialize.ts", () => {
});
});
test("calls handleErrorOnFirstInit if environment fetch fails initially", async () => {
test("calls handleErrorOnFirstSetup if environment fetch fails initially", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue(undefined),
update: vi.fn(),
@@ -283,12 +276,12 @@ describe("initialize.ts", () => {
error: { code: "forbidden", responseMessage: "No access" },
});
await expect(init({ environmentId: "envX", appUrl: "https://urlX" })).rejects.toThrow(
"Could not initialize formbricks"
await expect(setup({ environmentId: "envX", appUrl: "https://urlX" })).rejects.toThrow(
"Could not set up formbricks"
);
});
test("adds event listeners and sets isInitialized", async () => {
test("adds event listeners and sets isSetup", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue({
environmentId: "env_abc",
@@ -302,30 +295,30 @@ describe("initialize.ts", () => {
getInstanceConfigMock.mockReturnValueOnce(mockConfig as unknown as RNConfig);
const result = await init({ environmentId: "env_abc", appUrl: "https://test.app" });
const result = await setup({ environmentId: "env_abc", appUrl: "https://test.app" });
expect(result.ok).toBe(true);
expect(addEventListeners).toHaveBeenCalled();
expect(addCleanupEventListeners).toHaveBeenCalled();
});
});
describe("checkInitialized()", () => {
test("returns err if not initialized", () => {
const res = checkInitialized();
describe("checkSetup()", () => {
test("returns err if not setup", () => {
const res = checkSetup();
expect(res.ok).toBe(false);
if (!res.ok) {
expect(res.error.code).toBe("not_initialized");
expect(res.error.code).toBe("not_setup");
}
});
test("returns ok if initialized", () => {
setIsInitialize(true);
const res = checkInitialized();
test("returns ok if setup", () => {
setIsSetup(true);
const res = checkSetup();
expect(res.ok).toBe(true);
});
});
describe("deinitalize()", () => {
describe("tearDown()", () => {
test("resets user state to default and removes event listeners", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue({
@@ -336,7 +329,7 @@ describe("initialize.ts", () => {
getInstanceConfigMock.mockReturnValueOnce(mockConfig as unknown as RNConfig);
await deinitalize();
await tearDown();
expect(mockConfig.update).toHaveBeenCalledWith(
expect.objectContaining({
@@ -347,14 +340,14 @@ describe("initialize.ts", () => {
});
});
describe("handleErrorOnFirstInit()", () => {
describe("handleErrorOnFirstSetup()", () => {
test("stores error state in AsyncStorage, throws error", async () => {
// We import the function directly
const errorObj = { code: "forbidden", responseMessage: "No access" };
await expect(async () => {
await handleErrorOnFirstInit(errorObj);
}).rejects.toThrow("Could not initialize formbricks");
await handleErrorOnFirstSetup(errorObj);
}).rejects.toThrow("Could not set up formbricks");
// AsyncStorage setItem should be called with the error config
expect(AsyncStorage.setItem).toHaveBeenCalledWith(

View File

@@ -1,9 +1,9 @@
import { type Mock, type MockInstance, beforeEach, describe, expect, test, vi } from "vitest";
import { RNConfig } from "@/lib/common/config";
import { deinitalize, init } from "@/lib/common/initialize";
import { Logger } from "@/lib/common/logger";
import { setup, tearDown } from "@/lib/common/setup";
import { UpdateQueue } from "@/lib/user/update-queue";
import { logout, logoutUser, setUserId } from "@/lib/user/user";
import { logout, setUserId } from "@/lib/user/user";
// Mock dependencies
vi.mock("@/lib/common/config", () => ({
@@ -32,9 +32,9 @@ vi.mock("@/lib/user/update-queue", () => ({
},
}));
vi.mock("@/lib/common/initialize", () => ({
deinitalize: vi.fn(),
init: vi.fn(),
vi.mock("@/lib/common/setup", () => ({
tearDown: vi.fn(),
setup: vi.fn(),
}));
describe("user.ts", () => {
@@ -115,15 +115,8 @@ describe("user.ts", () => {
});
});
describe("logoutUser", () => {
test("calls deinitalize", async () => {
await logoutUser();
expect(deinitalize).toHaveBeenCalled();
});
});
describe("logout", () => {
test("successfully reinitializes after logout", async () => {
test("successfully sets up formbricks after logout", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue({
environmentId: mockEnvironmentId,
@@ -134,19 +127,19 @@ describe("user.ts", () => {
getInstanceConfigMock.mockReturnValue(mockConfig as unknown as RNConfig);
(init as Mock).mockResolvedValue(undefined);
(setup as Mock).mockResolvedValue(undefined);
const result = await logout();
expect(deinitalize).toHaveBeenCalled();
expect(init).toHaveBeenCalledWith({
expect(tearDown).toHaveBeenCalled();
expect(setup).toHaveBeenCalledWith({
environmentId: mockEnvironmentId,
appUrl: mockAppUrl,
});
expect(result.ok).toBe(true);
});
test("returns error if initialization fails", async () => {
test("returns error if setup fails", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue({
environmentId: mockEnvironmentId,
@@ -158,12 +151,12 @@ describe("user.ts", () => {
getInstanceConfigMock.mockReturnValue(mockConfig as unknown as RNConfig);
const mockError = { code: "network_error", message: "Failed to connect" };
(init as Mock).mockRejectedValue(mockError);
(setup as Mock).mockRejectedValue(mockError);
const result = await logout();
expect(deinitalize).toHaveBeenCalled();
expect(init).toHaveBeenCalledWith({
expect(tearDown).toHaveBeenCalled();
expect(setup).toHaveBeenCalledWith({
environmentId: mockEnvironmentId,
appUrl: mockAppUrl,
});

View File

@@ -1,6 +1,6 @@
import { RNConfig } from "@/lib/common/config";
import { deinitalize, init } from "@/lib/common/initialize";
import { Logger } from "@/lib/common/logger";
import { setup, tearDown } from "@/lib/common/setup";
import { UpdateQueue } from "@/lib/user/update-queue";
import { type ApiErrorResponse, type NetworkError, type Result, err, okVoid } from "@/types/error";
@@ -31,10 +31,6 @@ export const setUserId = async (userId: string): Promise<Result<void, ApiErrorRe
return okVoid();
};
export const logoutUser = async (): Promise<void> => {
await deinitalize();
};
export const logout = async (): Promise<Result<void, NetworkError>> => {
const logger = Logger.getInstance();
const appConfig = RNConfig.getInstance();
@@ -52,10 +48,11 @@ export const logout = async (): Promise<Result<void, NetworkError>> => {
appUrl: appConfig.get().appUrl,
};
void logoutUser();
// logout the user, remove user state and setup formbricks again
await tearDown();
try {
await init(initParams);
await setup(initParams);
return okVoid();
} catch (e) {
return err(e as NetworkError);

View File

@@ -54,8 +54,8 @@ export interface NetworkError {
url: URL;
responseMessage: string;
}
export interface NotInitializedError {
code: "not_initialized";
export interface NotSetupError {
code: "not_setup";
message: string;
}