Skip to content

Commit 98c6e89

Browse files
committed
adds workspace support and environmentId backwards compatibility
1 parent e6487bc commit 98c6e89

23 files changed

Lines changed: 686 additions & 422 deletions

File tree

packages/react-native/src/components/formbricks.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { SurveyStore } from "@/lib/survey/store";
88

99
interface FormbricksProps {
1010
appUrl: string;
11-
environmentId: string;
11+
/** @deprecated Use `workspaceId` instead. Still works as a backward-compatible alias. */
12+
environmentId?: string;
13+
workspaceId?: string;
1214
}
1315

1416
const surveyStore = SurveyStore.getInstance();
@@ -17,12 +19,14 @@ const logger = Logger.getInstance();
1719
export function Formbricks({
1820
appUrl,
1921
environmentId,
22+
workspaceId,
2023
}: FormbricksProps): React.JSX.Element | null {
2124
// initializes sdk
2225
useEffect(() => {
2326
const setupFormbricks = async (): Promise<void> => {
2427
try {
2528
await setup({
29+
workspaceId,
2630
environmentId,
2731
appUrl,
2832
});
@@ -34,7 +38,7 @@ export function Formbricks({
3438
setupFormbricks().catch(() => {
3539
logger.debug("Initialization error");
3640
});
37-
}, [environmentId, appUrl]);
41+
}, [environmentId, workspaceId, appUrl]);
3842

3943
const subscribe = useCallback((callback: () => void) => {
4044
const unsubscribe = surveyStore.subscribe(callback);

packages/react-native/src/components/survey-web-view.tsx

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,18 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
9393
return null;
9494
}
9595

96-
const project = appConfig.get().environment.data.project;
97-
const styling = getStyling(project, props.survey);
98-
const isBrandingEnabled = project.inAppSurveyBranding;
96+
const settings = appConfig.get().workspace.data.settings;
97+
const styling = getStyling(settings, props.survey);
98+
const isBrandingEnabled = settings.inAppSurveyBranding;
9999

100100
const onCloseSurvey = (): void => {
101-
const { environment: environmentState, user: personState } =
102-
appConfig.get();
103-
const filteredSurveys = filterSurveys(environmentState, personState);
101+
const { workspace, user: userState } = appConfig.get();
102+
const filteredSurveys = filterSurveys(workspace, userState);
104103

105104
appConfig.update({
106105
...appConfig.get(),
107-
environment: environmentState,
108-
user: personState,
106+
workspace,
107+
user: userState,
109108
filteredSurveys,
110109
});
111110

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

116115
const surveyPlacement =
117-
props.survey.projectOverwrites?.placement ?? project.placement;
116+
props.survey.projectOverwrites?.placement ?? settings.placement;
118117
const clickOutside =
119118
props.survey.projectOverwrites?.clickOutsideClose ??
120-
project.clickOutsideClose;
121-
const overlay = props.survey.projectOverwrites?.overlay ?? project.overlay;
119+
settings.clickOutsideClose;
120+
const overlay = props.survey.projectOverwrites?.overlay ?? settings.overlay;
122121
const appUrl = appConfig.get().appUrl;
123122

124123
return (
@@ -141,7 +140,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
141140
originWhitelist={["https://*", "http://*"]}
142141
source={{
143142
html: renderHtml({
144-
environmentId: appConfig.get().environmentId,
143+
workspaceId: appConfig.get().workspaceId,
145144
contactId: appConfig.get().user.data.contactId ?? undefined,
146145
survey: props.survey,
147146
isBrandingEnabled,
@@ -214,7 +213,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
214213
const displays = [...existingDisplays, newDisplay];
215214
const previousConfig = appConfig.get();
216215

217-
const updatedPersonState = {
216+
const updatedUserState = {
218217
...previousConfig.user,
219218
data: {
220219
...previousConfig.user.data,
@@ -224,14 +223,14 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
224223
};
225224

226225
const filteredSurveys = filterSurveys(
227-
previousConfig.environment,
228-
updatedPersonState,
226+
previousConfig.workspace,
227+
updatedUserState,
229228
);
230229

231230
appConfig.update({
232231
...previousConfig,
233-
environment: previousConfig.environment,
234-
user: updatedPersonState,
232+
workspace: previousConfig.workspace,
233+
user: updatedUserState,
235234
filteredSurveys,
236235
});
237236
}
@@ -246,13 +245,13 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
246245
};
247246

248247
const filteredSurveys = filterSurveys(
249-
appConfig.get().environment,
248+
appConfig.get().workspace,
250249
newPersonState,
251250
);
252251

253252
appConfig.update({
254253
...appConfig.get(),
255-
environment: appConfig.get().environment,
254+
workspace: appConfig.get().workspace,
256255
user: newPersonState,
257256
filteredSurveys,
258257
});
@@ -380,7 +379,7 @@ const renderHtml = (
380379
onResponseCreated,
381380
onClose,
382381
};
383-
382+
384383
window.formbricksSurveys.renderSurvey(surveyProps);
385384
}
386385

packages/react-native/src/lib/common/api.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
ApiSuccessResponse,
55
CreateOrUpdateUserResponse,
66
} from "@/types/api";
7-
import type { TEnvironmentState } from "@/types/config";
7+
import type { TWorkspaceState } from "@/types/config";
88
import { type ApiErrorResponse, err, ok, type Result } from "@/types/error";
99

1010
export const makeRequest = async <T>(
@@ -57,20 +57,20 @@ export const makeRequest = async <T>(
5757
// Simple API client using fetch
5858
export class ApiClient {
5959
private readonly appUrl: string;
60-
private readonly environmentId: string;
60+
private readonly workspaceId: string;
6161
private readonly isDebug: boolean;
6262

6363
constructor({
6464
appUrl,
65-
environmentId,
65+
workspaceId,
6666
isDebug = false,
6767
}: {
6868
appUrl: string;
69-
environmentId: string;
69+
workspaceId: string;
7070
isDebug: boolean;
7171
}) {
7272
this.appUrl = appUrl;
73-
this.environmentId = environmentId;
73+
this.workspaceId = workspaceId;
7474
this.isDebug = isDebug;
7575
}
7676

@@ -82,7 +82,7 @@ export class ApiClient {
8282
// The backend will use the JS type to determine the attribute data type
8383
return makeRequest(
8484
this.appUrl,
85-
`/api/v2/client/${this.environmentId}/user`,
85+
`/api/v2/client/${this.workspaceId}/user`,
8686
"POST",
8787
{
8888
userId: userUpdateInput.userId,
@@ -92,12 +92,12 @@ export class ApiClient {
9292
);
9393
}
9494

95-
async getEnvironmentState(): Promise<
96-
Result<TEnvironmentState, ApiErrorResponse>
95+
async getWorkspaceState(): Promise<
96+
Result<TWorkspaceState, ApiErrorResponse>
9797
> {
9898
return makeRequest(
9999
this.appUrl,
100-
`/api/v1/client/${this.environmentId}/environment`,
100+
`/api/v1/client/${this.workspaceId}/environment`,
101101
"GET",
102102
undefined,
103103
this.isDebug,

packages/react-native/src/lib/common/config.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
/* eslint-disable no-console -- Required for error logging */
22
import { AsyncStorage } from "@/lib/common/storage";
33
import { wrapThrowsAsync } from "@/lib/common/utils";
4-
import type { TConfig, TConfigUpdateInput } from "@/types/config";
4+
import type {
5+
TConfig,
6+
TConfigUpdateInput,
7+
TLegacyConfig,
8+
} from "@/types/config";
59
import { err, ok, type Result } from "@/types/error";
610

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

13+
/**
14+
* Migrate AsyncStorage config from the pre-workspace rename shape:
15+
* - `environmentId` → `workspaceId`
16+
* - `environment` → `workspace`
17+
* - `environment.data.project` (or legacy `workspace`) → `workspace.data.settings`
18+
*/
19+
const migrateLegacyConfig = (parsed: TLegacyConfig): TConfig => {
20+
// Already in the new shape
21+
if (parsed.workspace && parsed.workspaceId) {
22+
return parsed;
23+
}
24+
25+
const legacyEnvironment = parsed.environment;
26+
const migratedWorkspace = legacyEnvironment
27+
? (() => {
28+
const envData = legacyEnvironment.data;
29+
const settings = envData.settings ?? envData.project;
30+
return {
31+
expiresAt: legacyEnvironment.expiresAt,
32+
data: {
33+
surveys: envData.surveys,
34+
actionClasses: envData.actionClasses,
35+
settings: settings as TConfig["workspace"]["data"]["settings"],
36+
},
37+
} as TConfig["workspace"];
38+
})()
39+
: undefined;
40+
41+
const { environmentId, environment, ...rest } = parsed;
42+
43+
return {
44+
...(rest as unknown as TConfig),
45+
workspaceId:
46+
(rest as unknown as TConfig).workspaceId ?? (environmentId as string),
47+
...(migratedWorkspace ? { workspace: migratedWorkspace } : {}),
48+
} as TConfig;
49+
};
50+
951
export class RNConfig {
1052
private static instance: RNConfig | null = null;
1153

@@ -57,13 +99,15 @@ export class RNConfig {
5799
try {
58100
const savedConfig = await AsyncStorage.getItem(RN_ASYNC_STORAGE_KEY);
59101
if (savedConfig) {
60-
const parsedConfig = JSON.parse(savedConfig) as TConfig;
102+
const parsedConfig = migrateLegacyConfig(
103+
JSON.parse(savedConfig) as TLegacyConfig,
104+
);
61105

62106
// check if the config has expired
63107
if (
64108
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- need to check if expiresAt is set
65-
parsedConfig.environment.expiresAt &&
66-
new Date(parsedConfig.environment.expiresAt) <= new Date()
109+
parsedConfig.workspace.expiresAt &&
110+
new Date(parsedConfig.workspace.expiresAt) <= new Date()
67111
) {
68112
return err(new Error("Config in local storage has expired"));
69113
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
import {
2-
addEnvironmentStateExpiryCheckListener,
3-
clearEnvironmentStateExpiryCheckListener,
4-
} from "@/lib/environment/state";
51
import {
62
addUserStateExpiryCheckListener,
73
clearUserStateExpiryCheckListener,
84
} from "@/lib/user/state";
5+
import {
6+
addWorkspaceStateExpiryCheckListener,
7+
clearWorkspaceStateExpiryCheckListener,
8+
} from "@/lib/workspace/state";
99

1010
let areRemoveEventListenersAdded = false;
1111

1212
export const addEventListeners = (): void => {
13-
void addEnvironmentStateExpiryCheckListener();
13+
void addWorkspaceStateExpiryCheckListener();
1414
void addUserStateExpiryCheckListener();
1515
};
1616

1717
export const addCleanupEventListeners = (): void => {
1818
if (areRemoveEventListenersAdded) return;
19-
clearEnvironmentStateExpiryCheckListener();
19+
clearWorkspaceStateExpiryCheckListener();
2020
clearUserStateExpiryCheckListener();
2121
areRemoveEventListenersAdded = true;
2222
};
2323

2424
export const removeCleanupEventListeners = (): void => {
2525
if (!areRemoveEventListenersAdded) return;
26-
clearEnvironmentStateExpiryCheckListener();
26+
clearWorkspaceStateExpiryCheckListener();
2727
clearUserStateExpiryCheckListener();
2828
areRemoveEventListenersAdded = false;
2929
};
3030

3131
export const removeAllEventListeners = (): void => {
32-
clearEnvironmentStateExpiryCheckListener();
32+
clearWorkspaceStateExpiryCheckListener();
3333
clearUserStateExpiryCheckListener();
3434
removeCleanupEventListeners();
3535
};

0 commit comments

Comments
 (0)