Skip to content

Commit 2b5cc02

Browse files
authored
fix: sonarqube reliability and maintainability issues (#7)
* some sonar fixes * fix: moves appConfig to async handling * fixes sonarqube issues * fixes the remaining sonarqube issues
1 parent 630a164 commit 2b5cc02

31 files changed

+888
-504
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: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import React, { type JSX, useEffect, useRef, useState } from "react";
99
import { KeyboardAvoidingView, Modal, View, StyleSheet } 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

@@ -25,16 +24,27 @@ export function SurveyWebView(
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+
};
35+
36+
void fetchConfig();
37+
}, []);
3138

32-
const styling = getStyling(project, props.survey);
33-
const isBrandingEnabled = project.inAppSurveyBranding;
3439
const isMultiLanguageSurvey = props.survey.languages.length > 1;
35-
const [languageCode, setLanguageCode] = useState("default");
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(props.survey, language);
4050
if (!displayLanguage) {
@@ -51,7 +61,7 @@ export function SurveyWebView(
5161
} else {
5262
setIsSurveyRunning(true);
5363
}
54-
}, [isMultiLanguageSurvey, language, props.survey]);
64+
}, [isMultiLanguageSurvey, props.survey, appConfig]);
5565

5666
useEffect(() => {
5767
if (!isSurveyRunning) {
@@ -75,6 +85,14 @@ export function SurveyWebView(
7585
setShowSurvey(true);
7686
}, [props.survey.delay, isSurveyRunning, props.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: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { wrapThrowsAsync } from "@/lib/common/utils";
2-
import { ApiResponse, ApiSuccessResponse, CreateOrUpdateUserResponse } from "@/types/api";
2+
import {
3+
ApiResponse,
4+
ApiSuccessResponse,
5+
CreateOrUpdateUserResponse,
6+
} from "@/types/api";
37
import { TEnvironmentState } from "@/types/config";
48
import { ApiErrorResponse, Result, err, ok } from "@/types/error";
59

@@ -40,7 +44,9 @@ export const makeRequest = async <T>(
4044
status: response.status,
4145
message: errorResponse.message || "Something went wrong",
4246
url,
43-
...(Object.keys(errorResponse.details ?? {}).length > 0 && { details: errorResponse.details }),
47+
...(Object.keys(errorResponse.details ?? {}).length > 0 && {
48+
details: errorResponse.details,
49+
}),
4450
});
4551
}
4652

@@ -50,9 +56,9 @@ export const makeRequest = async <T>(
5056

5157
// Simple API client using fetch
5258
export class ApiClient {
53-
private appUrl: string;
54-
private environmentId: string;
55-
private isDebug: boolean;
59+
private readonly appUrl: string;
60+
private readonly environmentId: string;
61+
private readonly isDebug: boolean;
5662

5763
constructor({
5864
appUrl,
@@ -90,7 +96,9 @@ export class ApiClient {
9096
);
9197
}
9298

93-
async getEnvironmentState(): Promise<Result<TEnvironmentState, ApiErrorResponse>> {
99+
async getEnvironmentState(): Promise<
100+
Result<TEnvironmentState, ApiErrorResponse>
101+
> {
94102
return makeRequest(
95103
this.appUrl,
96104
`/api/v1/client/${this.environmentId}/environment`,

packages/react-native/src/lib/common/command-queue.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { wrapThrowsAsync } from "@/lib/common/utils";
44
import type { Result } from "@/types/error";
55

66
export class CommandQueue {
7-
private queue: {
8-
command: (...args: any[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>;
7+
private readonly queue: {
8+
command: (
9+
...args: any[]
10+
) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>;
911
checkSetup: boolean;
1012
commandArgs: any[];
1113
}[] = [];
@@ -14,11 +16,17 @@ export class CommandQueue {
1416
private commandPromise: Promise<void> | null = null;
1517

1618
public add<A>(
17-
command: (...args: A[]) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>,
19+
command: (
20+
...args: A[]
21+
) => Promise<Result<void, unknown>> | Result<void, unknown> | Promise<void>,
1822
shouldCheckSetup = true,
1923
...args: A[]
2024
): void {
21-
this.queue.push({ command, checkSetup: shouldCheckSetup, commandArgs: args });
25+
this.queue.push({
26+
command,
27+
checkSetup: shouldCheckSetup,
28+
commandArgs: args,
29+
});
2230

2331
if (!this.running) {
2432
this.commandPromise = new Promise((resolve) => {
@@ -52,7 +60,10 @@ export class CommandQueue {
5260
}
5361

5462
const executeCommand = async (): Promise<Result<void, unknown>> => {
55-
return (await currentItem.command.apply(null, currentItem.commandArgs)) as Result<void, unknown>;
63+
return (await currentItem.command.apply(
64+
null,
65+
currentItem.commandArgs
66+
)) as Result<void, unknown>;
5667
};
5768

5869
const result = await wrapThrowsAsync(executeCommand)();

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +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-
});
24-
}
14+
private constructor() {}
2515

26-
static getInstance(): RNConfig {
27-
if (!RNConfig.instance) {
28-
RNConfig.instance = new RNConfig();
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);
2924
}
25+
}
3026

27+
static async getInstance(): Promise<RNConfig> {
28+
RNConfig.instance ??= new RNConfig();
29+
await RNConfig.instance.init();
3130
return RNConfig.instance;
3231
}
3332

@@ -46,7 +45,9 @@ export class RNConfig {
4645

4746
public get(): TConfig {
4847
if (!this.config) {
49-
throw new Error("config is null, maybe the init function was not called?");
48+
throw new Error(
49+
"config is null, maybe the init function was not called?"
50+
);
5051
}
5152
return this.config;
5253
}
@@ -77,7 +78,10 @@ export class RNConfig {
7778

7879
private async saveToStorage(): Promise<Result<void>> {
7980
return wrapThrowsAsync(async () => {
80-
await AsyncStorage.setItem(RN_ASYNC_STORAGE_KEY, JSON.stringify(this.config));
81+
await AsyncStorage.setItem(
82+
RN_ASYNC_STORAGE_KEY,
83+
JSON.stringify(this.config)
84+
);
8185
})();
8286
}
8387

packages/react-native/src/lib/common/file-upload.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
/* eslint-disable no-console -- used for error logging */
2-
import { type TUploadFileConfig, type TUploadFileResponse } from "@/types/storage";
2+
import {
3+
type TUploadFileConfig,
4+
type TUploadFileResponse,
5+
} from "@/types/storage";
36

47
export class StorageAPI {
5-
private appUrl: string;
6-
private environmentId: string;
8+
private readonly appUrl: string;
9+
private readonly environmentId: string;
710

811
constructor(appUrl: string, environmentId: string) {
912
this.appUrl = appUrl;
@@ -29,13 +32,16 @@ export class StorageAPI {
2932
surveyId,
3033
};
3134

32-
const response = await fetch(`${this.appUrl}/api/v1/client/${this.environmentId}/storage`, {
33-
method: "POST",
34-
headers: {
35-
"Content-Type": "application/json",
36-
},
37-
body: JSON.stringify(payload),
38-
});
35+
const response = await fetch(
36+
`${this.appUrl}/api/v1/client/${this.environmentId}/storage`,
37+
{
38+
method: "POST",
39+
headers: {
40+
"Content-Type": "application/json",
41+
},
42+
body: JSON.stringify(payload),
43+
}
44+
);
3945

4046
if (!response.ok) {
4147
throw new Error(`Upload failed with status: ${String(response.status)}`);
@@ -45,7 +51,13 @@ export class StorageAPI {
4551

4652
const { data } = json;
4753

48-
const { signedUrl, fileUrl, signingData, presignedFields, updatedFileName } = data;
54+
const {
55+
signedUrl,
56+
fileUrl,
57+
signingData,
58+
presignedFields,
59+
updatedFileName,
60+
} = data;
4961

5062
let localUploadDetails: Record<string, string> = {};
5163

@@ -86,7 +98,10 @@ export class StorageAPI {
8698

8799
let uploadResponse: Response = {} as Response;
88100

89-
const signedUrlCopy = signedUrl.replace("http://localhost:3000", this.appUrl);
101+
const signedUrlCopy = signedUrl.replace(
102+
"http://localhost:3000",
103+
this.appUrl
104+
);
90105

91106
try {
92107
uploadResponse = await fetch(signedUrlCopy, {
@@ -114,12 +129,16 @@ export class StorageAPI {
114129
// if s3 is used, we'll use the text response:
115130
const errorText = await uploadResponse.text();
116131
if (presignedFields && errorText.includes("EntityTooLarge")) {
117-
const error = new Error("File size exceeds the size limit for your plan");
132+
const error = new Error(
133+
"File size exceeds the size limit for your plan"
134+
);
118135
error.name = "FileTooLargeError";
119136
throw error;
120137
}
121138

122-
throw new Error(`Upload failed with status: ${String(uploadResponse.status)}`);
139+
throw new Error(
140+
`Upload failed with status: ${String(uploadResponse.status)}`
141+
);
123142
}
124143

125144
return fileUrl;

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ export class Logger {
1010
private logLevel: LogLevel = "error";
1111

1212
static getInstance(): Logger {
13-
if (!Logger.instance) {
14-
Logger.instance = new Logger();
15-
}
13+
Logger.instance ??= new Logger();
1614
return Logger.instance;
1715
}
1816

0 commit comments

Comments
 (0)