Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/react-native/src/components/formbricks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

interface FormbricksProps {
appUrl: string;
environmentId: string;
/** @deprecated Use `workspaceId` instead. Still works as a backward-compatible alias. */
environmentId?: string;
workspaceId?: string;
}
Comment thread
pandeymangg marked this conversation as resolved.
Outdated

const surveyStore = SurveyStore.getInstance();
Expand All @@ -16,13 +18,15 @@

export function Formbricks({
appUrl,
environmentId,

Check warning on line 21 in packages/react-native/src/components/formbricks.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'environmentId' is deprecated.

See more on https://sonarcloud.io/project/issues?id=formbricks_react-native&issues=AZ25z_8SqnA9zNiZr956&open=AZ25z_8SqnA9zNiZr956&pullRequest=59
workspaceId,
}: FormbricksProps): React.JSX.Element | null {
// initializes sdk
useEffect(() => {
const setupFormbricks = async (): Promise<void> => {
try {
await setup({
workspaceId,
environmentId,
appUrl,
});
Expand All @@ -34,7 +38,7 @@
setupFormbricks().catch(() => {
logger.debug("Initialization error");
});
}, [environmentId, appUrl]);
}, [environmentId, workspaceId, appUrl]);

const subscribe = useCallback((callback: () => void) => {
const unsubscribe = surveyStore.subscribe(callback);
Expand Down
39 changes: 19 additions & 20 deletions packages/react-native/src/components/survey-web-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,18 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
return null;
}

const project = appConfig.get().environment.data.project;
const styling = getStyling(project, props.survey);
const isBrandingEnabled = project.inAppSurveyBranding;
const settings = appConfig.get().workspace.data.settings;
const styling = getStyling(settings, props.survey);
const isBrandingEnabled = settings.inAppSurveyBranding;

const onCloseSurvey = (): void => {
const { environment: environmentState, user: personState } =
appConfig.get();
const filteredSurveys = filterSurveys(environmentState, personState);
const { workspace, user: userState } = appConfig.get();
const filteredSurveys = filterSurveys(workspace, userState);

appConfig.update({
...appConfig.get(),
environment: environmentState,
user: personState,
workspace,
user: userState,
filteredSurveys,
});

Expand All @@ -114,11 +113,11 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
};

const surveyPlacement =
props.survey.projectOverwrites?.placement ?? project.placement;
props.survey.projectOverwrites?.placement ?? settings.placement;
const clickOutside =
props.survey.projectOverwrites?.clickOutsideClose ??
project.clickOutsideClose;
const overlay = props.survey.projectOverwrites?.overlay ?? project.overlay;
settings.clickOutsideClose;
const overlay = props.survey.projectOverwrites?.overlay ?? settings.overlay;
const appUrl = appConfig.get().appUrl;

return (
Expand All @@ -141,7 +140,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
originWhitelist={["https://*", "http://*"]}
source={{
html: renderHtml({
environmentId: appConfig.get().environmentId,
workspaceId: appConfig.get().workspaceId,
contactId: appConfig.get().user.data.contactId ?? undefined,
survey: props.survey,
isBrandingEnabled,
Expand Down Expand Up @@ -214,7 +213,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
const displays = [...existingDisplays, newDisplay];
const previousConfig = appConfig.get();

const updatedPersonState = {
const updatedUserState = {
...previousConfig.user,
data: {
...previousConfig.user.data,
Expand All @@ -224,14 +223,14 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
};

const filteredSurveys = filterSurveys(
previousConfig.environment,
updatedPersonState,
previousConfig.workspace,
updatedUserState,
);

appConfig.update({
...previousConfig,
environment: previousConfig.environment,
user: updatedPersonState,
workspace: previousConfig.workspace,
user: updatedUserState,
filteredSurveys,
});
}
Expand All @@ -246,13 +245,13 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
};

const filteredSurveys = filterSurveys(
appConfig.get().environment,
appConfig.get().workspace,
newPersonState,
);

appConfig.update({
...appConfig.get(),
environment: appConfig.get().environment,
workspace: appConfig.get().workspace,
user: newPersonState,
filteredSurveys,
});
Expand Down Expand Up @@ -380,7 +379,7 @@ const renderHtml = (
onResponseCreated,
onClose,
};

window.formbricksSurveys.renderSurvey(surveyProps);
}

Expand Down
18 changes: 9 additions & 9 deletions packages/react-native/src/lib/common/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
ApiSuccessResponse,
CreateOrUpdateUserResponse,
} from "@/types/api";
import type { TEnvironmentState } from "@/types/config";
import type { TWorkspaceState } from "@/types/config";
import { type ApiErrorResponse, err, ok, type Result } from "@/types/error";

export const makeRequest = async <T>(
Expand Down Expand Up @@ -57,20 +57,20 @@ export const makeRequest = async <T>(
// Simple API client using fetch
export class ApiClient {
private readonly appUrl: string;
private readonly environmentId: string;
private readonly workspaceId: string;
private readonly isDebug: boolean;

constructor({
appUrl,
environmentId,
workspaceId,
isDebug = false,
}: {
appUrl: string;
environmentId: string;
workspaceId: string;
isDebug: boolean;
}) {
this.appUrl = appUrl;
this.environmentId = environmentId;
this.workspaceId = workspaceId;
this.isDebug = isDebug;
}

Expand All @@ -82,7 +82,7 @@ export class ApiClient {
// The backend will use the JS type to determine the attribute data type
return makeRequest(
this.appUrl,
`/api/v2/client/${this.environmentId}/user`,
`/api/v2/client/${this.workspaceId}/user`,
"POST",
{
userId: userUpdateInput.userId,
Expand All @@ -92,12 +92,12 @@ export class ApiClient {
);
}

async getEnvironmentState(): Promise<
Result<TEnvironmentState, ApiErrorResponse>
async getWorkspaceState(): Promise<
Result<TWorkspaceState, ApiErrorResponse>
> {
return makeRequest(
this.appUrl,
`/api/v1/client/${this.environmentId}/environment`,
`/api/v1/client/${this.workspaceId}/environment`,
"GET",
undefined,
this.isDebug,
Expand Down
52 changes: 48 additions & 4 deletions packages/react-native/src/lib/common/config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
/* eslint-disable no-console -- Required for error logging */
import { AsyncStorage } from "@/lib/common/storage";
import { wrapThrowsAsync } from "@/lib/common/utils";
import type { TConfig, TConfigUpdateInput } from "@/types/config";
import type {
TConfig,
TConfigUpdateInput,
TLegacyConfig,
} from "@/types/config";
import { err, ok, type Result } from "@/types/error";

export const RN_ASYNC_STORAGE_KEY = "formbricks-react-native";

/**
* Migrate AsyncStorage config from the pre-workspace rename shape:
* - `environmentId` → `workspaceId`
* - `environment` → `workspace`
* - `environment.data.project` (or legacy `workspace`) → `workspace.data.settings`
*/
const migrateLegacyConfig = (parsed: TLegacyConfig): TConfig => {
// Already in the new shape
if (parsed.workspace && parsed.workspaceId) {
return parsed;
}

const legacyEnvironment = parsed.environment;
const migratedWorkspace = legacyEnvironment
? (() => {
const envData = legacyEnvironment.data;
const settings = envData.settings ?? envData.project;
return {
expiresAt: legacyEnvironment.expiresAt,
data: {
surveys: envData.surveys,
actionClasses: envData.actionClasses,
settings: settings as TConfig["workspace"]["data"]["settings"],
},
} as TConfig["workspace"];
})()
: undefined;

const { environmentId, environment, ...rest } = parsed;

return {
...(rest as unknown as TConfig),
workspaceId:
(rest as unknown as TConfig).workspaceId ?? (environmentId as string),
...(migratedWorkspace ? { workspace: migratedWorkspace } : {}),
} as TConfig;
};

export class RNConfig {
private static instance: RNConfig | null = null;

Expand Down Expand Up @@ -57,13 +99,15 @@ export class RNConfig {
try {
const savedConfig = await AsyncStorage.getItem(RN_ASYNC_STORAGE_KEY);
if (savedConfig) {
const parsedConfig = JSON.parse(savedConfig) as TConfig;
const parsedConfig = migrateLegacyConfig(
JSON.parse(savedConfig) as TLegacyConfig,
);

// check if the config has expired
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- need to check if expiresAt is set
parsedConfig.environment.expiresAt &&
new Date(parsedConfig.environment.expiresAt) <= new Date()
parsedConfig.workspace.expiresAt &&
new Date(parsedConfig.workspace.expiresAt) <= new Date()
) {
return err(new Error("Config in local storage has expired"));
}
Expand Down
16 changes: 8 additions & 8 deletions packages/react-native/src/lib/common/event-listeners.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import {
addEnvironmentStateExpiryCheckListener,
clearEnvironmentStateExpiryCheckListener,
} from "@/lib/environment/state";
import {
addUserStateExpiryCheckListener,
clearUserStateExpiryCheckListener,
} from "@/lib/user/state";
import {
addWorkspaceStateExpiryCheckListener,
clearWorkspaceStateExpiryCheckListener,
} from "@/lib/workspace/state";

let areRemoveEventListenersAdded = false;

export const addEventListeners = (): void => {
void addEnvironmentStateExpiryCheckListener();
void addWorkspaceStateExpiryCheckListener();
void addUserStateExpiryCheckListener();
};

export const addCleanupEventListeners = (): void => {
if (areRemoveEventListenersAdded) return;
clearEnvironmentStateExpiryCheckListener();
clearWorkspaceStateExpiryCheckListener();
clearUserStateExpiryCheckListener();
areRemoveEventListenersAdded = true;
};

export const removeCleanupEventListeners = (): void => {
if (!areRemoveEventListenersAdded) return;
clearEnvironmentStateExpiryCheckListener();
clearWorkspaceStateExpiryCheckListener();
clearUserStateExpiryCheckListener();
areRemoveEventListenersAdded = false;
};

export const removeAllEventListeners = (): void => {
clearEnvironmentStateExpiryCheckListener();
clearWorkspaceStateExpiryCheckListener();
clearUserStateExpiryCheckListener();
removeCleanupEventListeners();
};
Loading
Loading