Skip to content

Commit dee3928

Browse files
committed
fixes coderabbit feedback
1 parent 98c6e89 commit dee3928

9 files changed

Lines changed: 143 additions & 81 deletions

File tree

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ import { Logger } from "@/lib/common/logger";
66
import { setup } from "@/lib/common/setup";
77
import { SurveyStore } from "@/lib/survey/store";
88

9-
interface FormbricksProps {
10-
appUrl: string;
11-
/** @deprecated Use `workspaceId` instead. Still works as a backward-compatible alias. */
12-
environmentId?: string;
13-
workspaceId?: string;
14-
}
9+
type FormbricksProps = { appUrl: string } & (
10+
| {
11+
workspaceId: string;
12+
environmentId?: never;
13+
}
14+
| {
15+
/** @deprecated Use `workspaceId` instead. Still works as a backward-compatible alias. */
16+
environmentId: string;
17+
workspaceId?: never;
18+
}
19+
);
1520

1621
const surveyStore = SurveyStore.getInstance();
1722
const logger = Logger.getInstance();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ async function syncUserStateIfExpired(
120120
message: "Error updating user state",
121121
status: 500,
122122
url: new URL(
123-
`${configInput.appUrl}/api/v1/client/${configInput.workspaceId}/update/contacts/${userState.data.userId}`,
123+
`${configInput.appUrl}/api/v2/client/${configInput.workspaceId}/user`,
124124
),
125125
responseMessage: "Unknown error",
126126
} as const);

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,63 @@ describe("RNConfig", () => {
7575
}
7676
});
7777

78+
test("loadFromStorage() migrates legacy { environmentId, environment } shape", async () => {
79+
const { workspaceId, workspace, ...rest } = mockConfig;
80+
const legacyStored = {
81+
...rest,
82+
environmentId: workspaceId,
83+
environment: workspace,
84+
};
85+
86+
vi.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(
87+
JSON.stringify(legacyStored),
88+
);
89+
90+
const result = await configInstance.loadFromStorage();
91+
expect(result.ok).toBe(true);
92+
if (result.ok) {
93+
expect(result.data.workspaceId).toBe(workspaceId);
94+
expect(result.data.workspace.data.settings).toEqual(
95+
workspace.data.settings,
96+
);
97+
expect(result.data.workspace.data.surveys).toEqual(
98+
workspace.data.surveys,
99+
);
100+
expect(
101+
(result.data as unknown as Record<string, unknown>).environmentId,
102+
).toBeUndefined();
103+
expect(
104+
(result.data as unknown as Record<string, unknown>).environment,
105+
).toBeUndefined();
106+
}
107+
});
108+
109+
test("loadFromStorage() migrates legacy environment.data.project to workspace.data.settings", async () => {
110+
const { workspaceId, workspace, ...rest } = mockConfig;
111+
const { settings, ...envDataWithoutSettings } = workspace.data;
112+
const legacyStored = {
113+
...rest,
114+
environmentId: workspaceId,
115+
environment: {
116+
expiresAt: workspace.expiresAt,
117+
data: {
118+
...envDataWithoutSettings,
119+
project: settings,
120+
},
121+
},
122+
};
123+
124+
vi.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(
125+
JSON.stringify(legacyStored),
126+
);
127+
128+
const result = await configInstance.loadFromStorage();
129+
expect(result.ok).toBe(true);
130+
if (result.ok) {
131+
expect(result.data.workspace.data.settings).toEqual(settings);
132+
}
133+
});
134+
78135
test("loadFromStorage() returns err if no or invalid config in storage", async () => {
79136
// Simulate no data
80137
vi.spyOn(AsyncStorage, "getItem").mockResolvedValueOnce(null);

packages/react-native/src/lib/common/tests/setup.test.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,27 +222,30 @@ describe("setup.ts", () => {
222222

223223
test("logs deprecation warning when only environmentId is used", async () => {
224224
const mockConfig = {
225-
get: vi.fn().mockReturnValue({
226-
workspaceId: "env_123",
227-
appUrl: "https://my.url",
228-
workspace: {
229-
expiresAt: new Date(Date.now() - 5000),
230-
data: { actionClasses: [] },
231-
},
232-
user: { data: {}, expiresAt: null },
233-
status: { value: "success", expiresAt: null },
234-
}),
225+
get: vi.fn().mockReturnValue(undefined),
226+
resetConfig: vi.fn(),
235227
update: vi.fn(),
236228
};
237229

238230
getInstanceConfigMock.mockReturnValue(
239231
mockConfig as unknown as Promise<RNConfig>,
240232
);
241233

242-
await setup({
234+
(fetchWorkspaceState as unknown as Mock).mockResolvedValueOnce({
235+
ok: true,
236+
data: {
237+
data: { surveys: [] },
238+
expiresAt: new Date(Date.now() + 60000),
239+
},
240+
});
241+
242+
(filterSurveys as unknown as Mock).mockReturnValueOnce([]);
243+
244+
const result = await setup({
243245
environmentId: "env_123",
244246
appUrl: "https://my.url",
245247
});
248+
expect(result.ok).toBe(true);
246249
expect(mockLogger.debug).toHaveBeenCalledWith(
247250
"environmentId is deprecated and will be removed in a future version. Please use workspaceId instead.",
248251
);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type {
44
TWorkspaceStateSettings,
55
} from "@/types/config";
66
import type { Result } from "@/types/error";
7-
import type { TWorkspaceStyling } from "@/types/workspace";
87
import type { TSurvey } from "@/types/survey";
8+
import type { TWorkspaceStyling } from "@/types/workspace";
99

1010
// Helper function to calculate difference in days between two dates
1111
export const diffInDays = (date1: Date, date2: Date): number => {

packages/react-native/src/lib/user/update.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const sendUpdatesToBackend = async ({
2424
ApiErrorResponse
2525
>
2626
> => {
27-
const url = `${appUrl}/api/v1/client/${workspaceId}/user`;
27+
const url = `${appUrl}/api/v2/client/${workspaceId}/user`;
2828
const api = new ApiClient({ appUrl, workspaceId, isDebug: false });
2929

3030
try {
@@ -70,7 +70,7 @@ export const sendUpdates = async ({
7070

7171
const { appUrl, workspaceId } = config.get();
7272
// update endpoint call
73-
const url = `${appUrl}/api/v1/client/${workspaceId}/user`;
73+
const url = `${appUrl}/api/v2/client/${workspaceId}/user`;
7474

7575
try {
7676
const updatesResponse = await sendUpdatesToBackend({

packages/react-native/src/lib/workspace/state.ts

Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -75,68 +75,67 @@ export const fetchWorkspaceState = async ({
7575
/**
7676
* Add a listener to check if the workspace state has expired with a certain interval
7777
*/
78-
export const addWorkspaceStateExpiryCheckListener =
79-
async (): Promise<void> => {
80-
const appConfig = await RNConfig.getInstance();
81-
const logger = Logger.getInstance();
82-
83-
const updateInterval = 1000 * 60; // every minute
84-
85-
if (workspaceSyncIntervalId === null) {
86-
const intervalHandler = async (): Promise<void> => {
87-
const expiresAt = appConfig.get().workspace.expiresAt;
88-
89-
try {
90-
// check if the workspace state has not expired yet
91-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- expiresAt is checked for null
92-
if (expiresAt && new Date(expiresAt) >= new Date()) {
93-
return;
94-
}
95-
96-
logger.debug("Workspace state has expired. Starting sync.");
97-
98-
const personState = appConfig.get().user;
99-
const workspace = await fetchWorkspaceState({
100-
appUrl: appConfig.get().appUrl,
101-
workspaceId: appConfig.get().workspaceId,
102-
});
78+
export const addWorkspaceStateExpiryCheckListener = async (): Promise<void> => {
79+
const appConfig = await RNConfig.getInstance();
80+
const logger = Logger.getInstance();
81+
82+
const updateInterval = 1000 * 60; // every minute
83+
84+
if (workspaceSyncIntervalId === null) {
85+
const intervalHandler = async (): Promise<void> => {
86+
const expiresAt = appConfig.get().workspace.expiresAt;
87+
88+
try {
89+
// check if the workspace state has not expired yet
90+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- expiresAt is checked for null
91+
if (expiresAt && new Date(expiresAt) >= new Date()) {
92+
return;
93+
}
94+
95+
logger.debug("Workspace state has expired. Starting sync.");
96+
97+
const personState = appConfig.get().user;
98+
const workspace = await fetchWorkspaceState({
99+
appUrl: appConfig.get().appUrl,
100+
workspaceId: appConfig.get().workspaceId,
101+
});
102+
103+
if (workspace.ok) {
104+
const { data: state } = workspace;
105+
const filteredSurveys = filterSurveys(state, personState);
103106

104-
if (workspace.ok) {
105-
const { data: state } = workspace;
106-
const filteredSurveys = filterSurveys(state, personState);
107-
108-
appConfig.update({
109-
...appConfig.get(),
110-
workspace: state,
111-
filteredSurveys,
112-
});
113-
} else {
114-
// eslint-disable-next-line @typescript-eslint/only-throw-error -- error is an ApiErrorResponse
115-
throw workspace.error;
116-
}
117-
} catch (e) {
118-
console.error(`Error during expiry check: `, e);
119-
logger.debug("Extending config and try again later.");
120-
const existingConfig = appConfig.get();
121107
appConfig.update({
122-
...existingConfig,
123-
workspace: {
124-
...existingConfig.workspace,
125-
expiresAt: new Date(Date.now() + 1000 * 60 * 30), // 30 minutes
126-
},
108+
...appConfig.get(),
109+
workspace: state,
110+
filteredSurveys,
127111
});
112+
} else {
113+
// eslint-disable-next-line @typescript-eslint/only-throw-error -- error is an ApiErrorResponse
114+
throw workspace.error;
128115
}
129-
};
116+
} catch (e) {
117+
console.error(`Error during expiry check: `, e);
118+
logger.debug("Extending config and try again later.");
119+
const existingConfig = appConfig.get();
120+
appConfig.update({
121+
...existingConfig,
122+
workspace: {
123+
...existingConfig.workspace,
124+
expiresAt: new Date(Date.now() + 1000 * 60 * 30), // 30 minutes
125+
},
126+
});
127+
}
128+
};
130129

131-
workspaceSyncIntervalId = setInterval(
132-
() => void intervalHandler(),
133-
updateInterval,
134-
) as unknown as number;
135-
}
136-
};
130+
workspaceSyncIntervalId = setInterval(
131+
() => void intervalHandler(),
132+
updateInterval,
133+
) as unknown as number;
134+
}
135+
};
137136

138137
export const clearWorkspaceStateExpiryCheckListener = (): void => {
139-
if (workspaceSyncIntervalId) {
138+
if (workspaceSyncIntervalId !== null) {
140139
clearInterval(workspaceSyncIntervalId);
141140
workspaceSyncIntervalId = null;
142141
}

packages/react-native/src/types/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { z } from "zod";
22
import type { TResponseUpdate } from "@/types/response";
33
import type { TFileUploadParams } from "@/types/storage";
44
import type { TActionClass } from "./action-class";
5-
import type { TWorkspace, TWorkspaceStyling } from "./workspace";
65
import type { TSurvey } from "./survey";
6+
import type { TWorkspace, TWorkspaceStyling } from "./workspace";
77

88
export type TWorkspaceStateSettings = Pick<
99
TWorkspace,

packages/react-native/src/types/survey.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,8 @@ export interface SurveyInlineProps extends SurveyBaseProps {
5757
placement: "bottomLeft" | "bottomRight" | "topLeft" | "topRight" | "center";
5858
}
5959

60-
export interface SurveyContainerProps extends Omit<
61-
SurveyBaseProps,
62-
"onFileUpload"
63-
> {
60+
export interface SurveyContainerProps
61+
extends Omit<SurveyBaseProps, "onFileUpload"> {
6462
appUrl?: string;
6563
/** @deprecated Use `workspaceId` instead. Still works as a backward-compatible alias. */
6664
environmentId?: string;

0 commit comments

Comments
 (0)