Skip to content

Commit 9dc71fb

Browse files
committed
fix: moves appConfig to async handling
1 parent 6a82081 commit 9dc71fb

File tree

19 files changed

+3063
-2014
lines changed

19 files changed

+3063
-2014
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"devDependencies": {
1212
"prettier": "^3.5.3",
1313
"turbo": "^2.5.2",
14-
"typescript": "5.8.2"
14+
"typescript": "5.8.2",
15+
"rimraf": "6.0.1"
1516
},
1617
"packageManager": "[email protected]",
1718
"engines": {

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

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,42 @@ import React, { type JSX, useEffect, useRef, useState } from "react";
99
import { Modal } from "react-native";
1010
import { WebView, type WebViewMessageEvent } from "react-native-webview";
1111

12-
const appConfig = RNConfig.getInstance();
1312
const logger = Logger.getInstance();
1413
logger.configure({ logLevel: "debug" });
1514

1615
const surveyStore = SurveyStore.getInstance();
1716

1817
interface SurveyWebViewProps {
19-
survey: TSurvey;
18+
readonly survey: TSurvey;
2019
}
2120

22-
export function SurveyWebView({
23-
survey,
24-
}: SurveyWebViewProps): JSX.Element | undefined {
21+
export function SurveyWebView(
22+
props: SurveyWebViewProps
23+
): JSX.Element | undefined {
2524
const webViewRef = useRef(null);
2625
const [isSurveyRunning, setIsSurveyRunning] = useState(false);
2726
const [showSurvey, setShowSurvey] = useState(false);
27+
const [appConfig, setAppConfig] = useState<RNConfig | null>(null);
28+
const [languageCode, setLanguageCode] = useState("default");
2829

29-
const project = appConfig.get().environment.data.project;
30-
const language = appConfig.get().user.data.language;
30+
useEffect(() => {
31+
const fetchConfig = async () => {
32+
const config = await RNConfig.getInstance();
33+
setAppConfig(config);
34+
};
3135

32-
const styling = getStyling(project, survey);
33-
const isBrandingEnabled = project.inAppSurveyBranding;
34-
const isMultiLanguageSurvey = survey.languages.length > 1;
35-
const [languageCode, setLanguageCode] = useState("default");
36+
void fetchConfig();
37+
}, []);
38+
39+
const isMultiLanguageSurvey = props.survey.languages.length > 1;
3640

3741
useEffect(() => {
42+
if (!appConfig) {
43+
return;
44+
}
45+
46+
const language = appConfig.get().user.data.language;
47+
3848
if (isMultiLanguageSurvey) {
3949
const displayLanguage = getLanguageCode(survey, language);
4050
if (!displayLanguage) {
@@ -51,7 +61,7 @@ export function SurveyWebView({
5161
} else {
5262
setIsSurveyRunning(true);
5363
}
54-
}, [isMultiLanguageSurvey, language, survey]);
64+
}, [isMultiLanguageSurvey, props.survey, appConfig]);
5565

5666
useEffect(() => {
5767
if (!isSurveyRunning) {
@@ -75,6 +85,14 @@ export function SurveyWebView({
7585
setShowSurvey(true);
7686
}, [survey.delay, isSurveyRunning, survey.name]);
7787

88+
if (!appConfig) {
89+
return;
90+
}
91+
92+
const project = appConfig.get().environment.data.project;
93+
const styling = getStyling(project, props.survey);
94+
const isBrandingEnabled = project.inAppSurveyBranding;
95+
7896
const onCloseSurvey = (): void => {
7997
const { environment: environmentState, user: personState } =
8098
appConfig.get();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class ApiClient {
101101
> {
102102
return makeRequest(
103103
this.appUrl,
104-
`/api/v1/client/${this.environmentId}/environment`,
104+
`/api/v1/client/${this.environmentId}/environment?rand-${Date.now()}=true`,
105105
"GET",
106106
undefined,
107107
this.isDebug

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ export class RNConfig {
1111

1212
private config: TConfig | null = null;
1313

14-
private constructor() {
15-
this.loadFromStorage()
16-
.then((localConfig) => {
17-
if (localConfig.ok) {
18-
this.config = localConfig.data;
19-
}
20-
})
21-
.catch((e: unknown) => {
22-
console.error("Error loading config from storage", e);
23-
});
14+
private constructor() {}
15+
16+
public async init(): Promise<void> {
17+
try {
18+
const localConfig = await this.loadFromStorage();
19+
if (localConfig.ok) {
20+
this.config = localConfig.data;
21+
}
22+
} catch (e: unknown) {
23+
console.error("Error loading config from storage", e);
24+
}
2425
}
2526

26-
static getInstance(): RNConfig {
27+
static async getInstance(): Promise<RNConfig> {
2728
RNConfig.instance ??= new RNConfig();
28-
29+
await RNConfig.instance.init();
2930
return RNConfig.instance;
3031
}
3132

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

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@ import {
66
} from "@/lib/common/event-listeners";
77
import { Logger } from "@/lib/common/logger";
88
import { AsyncStorage } from "@/lib/common/storage";
9-
import { filterSurveys, isNowExpired, wrapThrowsAsync } from "@/lib/common/utils";
9+
import {
10+
filterSurveys,
11+
isNowExpired,
12+
wrapThrowsAsync,
13+
} from "@/lib/common/utils";
1014
import { fetchEnvironmentState } from "@/lib/environment/state";
1115
import { DEFAULT_USER_STATE_NO_USER_ID } from "@/lib/user/state";
1216
import { sendUpdatesToBackend } from "@/lib/user/update";
13-
import { type TConfig, type TConfigInput, type TEnvironmentState, type TUserState } from "@/types/config";
17+
import {
18+
type TConfig,
19+
type TConfigInput,
20+
type TEnvironmentState,
21+
type TUserState,
22+
} from "@/types/config";
1423
import {
1524
type MissingFieldError,
1625
type MissingPersonError,
@@ -27,7 +36,9 @@ export const setIsSetup = (state: boolean): void => {
2736
isSetup = state;
2837
};
2938

30-
export const migrateUserStateAddContactId = async (): Promise<{ changed: boolean }> => {
39+
export const migrateUserStateAddContactId = async (): Promise<{
40+
changed: boolean;
41+
}> => {
3142
const existingConfigString = await AsyncStorage.getItem(RN_ASYNC_STORAGE_KEY);
3243

3344
if (existingConfigString) {
@@ -39,7 +50,10 @@ export const migrateUserStateAddContactId = async (): Promise<{ changed: boolean
3950
}
4051

4152
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data could be undefined
42-
if (!existingConfig.user?.data?.contactId && existingConfig.user?.data?.userId) {
53+
if (
54+
!existingConfig.user?.data?.contactId &&
55+
existingConfig.user?.data?.userId
56+
) {
4357
return { changed: true };
4458
}
4559
}
@@ -49,15 +63,17 @@ export const migrateUserStateAddContactId = async (): Promise<{ changed: boolean
4963

5064
export const setup = async (
5165
configInput: TConfigInput
52-
): Promise<Result<void, MissingFieldError | NetworkError | MissingPersonError>> => {
53-
let appConfig = RNConfig.getInstance();
66+
): Promise<
67+
Result<void, MissingFieldError | NetworkError | MissingPersonError>
68+
> => {
69+
let appConfig = await RNConfig.getInstance();
5470
const logger = Logger.getInstance();
5571

5672
const { changed } = await migrateUserStateAddContactId();
5773

5874
if (changed) {
5975
await appConfig.resetConfig();
60-
appConfig = RNConfig.getInstance();
76+
appConfig = await RNConfig.getInstance();
6177
}
6278

6379
if (isSetup) {
@@ -119,7 +135,10 @@ export const setup = async (
119135
isEnvironmentStateExpired = true;
120136
}
121137

122-
if (existingConfig.user.expiresAt && isNowExpired(existingConfig.user.expiresAt)) {
138+
if (
139+
existingConfig.user.expiresAt &&
140+
isNowExpired(existingConfig.user.expiresAt)
141+
) {
123142
logger.debug("Person state expired. Syncing.");
124143
isUserStateExpired = true;
125144
}
@@ -145,7 +164,9 @@ export const setup = async (
145164
code: "network_error",
146165
message: "Error fetching environment state",
147166
status: 500,
148-
url: new URL(`${configInput.appUrl}/api/v1/client/${configInput.environmentId}/environment`),
167+
url: new URL(
168+
`${configInput.appUrl}/api/v1/client/${configInput.environmentId}/environment`
169+
),
149170
responseMessage: environmentStateResponse.error.message,
150171
});
151172
}
@@ -197,12 +218,16 @@ export const setup = async (
197218
});
198219

199220
const surveyNames = filteredSurveys.map((s) => s.name);
200-
logger.debug(`Fetched ${surveyNames.length.toString()} surveys during sync: ${surveyNames.join(", ")}`);
221+
logger.debug(
222+
`Fetched ${surveyNames.length.toString()} surveys during sync: ${surveyNames.join(", ")}`
223+
);
201224
} catch {
202225
logger.debug("Error during sync. Please try again.");
203226
}
204227
} else {
205-
logger.debug("No valid configuration found. Resetting config and creating new one.");
228+
logger.debug(
229+
"No valid configuration found. Resetting config and creating new one."
230+
);
206231
void appConfig.resetConfig();
207232
logger.debug("Syncing.");
208233

@@ -234,7 +259,9 @@ export const setup = async (
234259
filteredSurveys,
235260
});
236261
} catch (e) {
237-
await handleErrorOnFirstSetup(e as { code: string; responseMessage: string });
262+
await handleErrorOnFirstSetup(
263+
e as { code: string; responseMessage: string }
264+
);
238265
}
239266
}
240267

@@ -266,13 +293,22 @@ export const checkSetup = (): Result<void, NotSetupError> => {
266293
// eslint-disable-next-line @typescript-eslint/require-await -- disabled for now
267294
export const tearDown = async (): Promise<void> => {
268295
const logger = Logger.getInstance();
269-
const appConfig = RNConfig.getInstance();
296+
const appConfig = await RNConfig.getInstance();
270297

271298
logger.debug("Setting user state to default");
299+
300+
const { environment } = appConfig.get();
301+
302+
const filteredSurveys = filterSurveys(
303+
environment,
304+
DEFAULT_USER_STATE_NO_USER_ID
305+
);
306+
272307
// clear the user state and set it to the default value
273308
appConfig.update({
274309
...appConfig.get(),
275310
user: DEFAULT_USER_STATE_NO_USER_ID,
311+
filteredSurveys,
276312
});
277313

278314
setIsSetup(false);
@@ -288,7 +324,9 @@ export const handleErrorOnFirstSetup = async (e: {
288324
if (e.code === "forbidden") {
289325
logger.error(`Authorization error: ${e.responseMessage}`);
290326
} else {
291-
logger.error(`Error during first setup: ${e.code} - ${e.responseMessage}. Please try again later.`);
327+
logger.error(
328+
`Error during first setup: ${e.code} - ${e.responseMessage}. Please try again later.`
329+
);
292330
}
293331

294332
// put formbricks in error state (by creating a new config) and throw error
@@ -300,7 +338,10 @@ export const handleErrorOnFirstSetup = async (e: {
300338
};
301339

302340
await wrapThrowsAsync(async () => {
303-
await AsyncStorage.setItem(RN_ASYNC_STORAGE_KEY, JSON.stringify(initialErrorConfig));
341+
await AsyncStorage.setItem(
342+
RN_ASYNC_STORAGE_KEY,
343+
JSON.stringify(initialErrorConfig)
344+
);
304345
})();
305346

306347
throw new Error("Could not set up formbricks");

0 commit comments

Comments
 (0)